V2 (#1)
10
packages/demo/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
node_modules/**/*
|
||||
.expo/*
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
web-report/
|
||||
1
packages/demo/.watchmanconfig
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
144
packages/demo/App.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Button,
|
||||
KeyboardAvoidingView,
|
||||
SafeAreaView,
|
||||
AsyncStorage,
|
||||
} from 'react-native';
|
||||
import {
|
||||
DevTool,
|
||||
DevToolModal,
|
||||
log,
|
||||
network,
|
||||
show,
|
||||
} from 'react-native-debug-console/src';
|
||||
|
||||
network.attach();
|
||||
log.attach(true);
|
||||
|
||||
console.log('fooo');
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'https://google.com');
|
||||
xhr.send();
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'https://google.com/sdfsfsdfsfdf');
|
||||
xhr.send();
|
||||
|
||||
|
||||
console.log({
|
||||
hello: {
|
||||
world: 1,
|
||||
earth: 2,
|
||||
},
|
||||
});
|
||||
|
||||
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 (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<KeyboardAvoidingView style={styles.wrapper} behavior="padding">
|
||||
<View>
|
||||
<Button
|
||||
style={styles.button}
|
||||
title="console.log('hello')"
|
||||
onPress={() => {
|
||||
console.log('hello');
|
||||
}}
|
||||
/>
|
||||
<View style={{ height: 10 }} />
|
||||
<Button
|
||||
title="GET XHR (html)"
|
||||
style={styles.button}
|
||||
onPress={() => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'https://google.com');
|
||||
xhr.send();
|
||||
}}
|
||||
/>
|
||||
<View style={{ height: 10 }} />
|
||||
<View style={{ height: 10 }} />
|
||||
<Button
|
||||
title="GET XHR (json)"
|
||||
style={styles.button}
|
||||
onPress={() => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'https://api.github.com');
|
||||
xhr.send();
|
||||
}}
|
||||
/>
|
||||
<View style={{ height: 10 }} />
|
||||
<Button
|
||||
title="POST XHR"
|
||||
style={styles.button}
|
||||
onPress={() => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'https://google.com');
|
||||
xhr.setRequestHeader('content-type', 'application/json');
|
||||
xhr.send(JSON.stringify({
|
||||
hello: 'world',
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
<View style={{ height: 10 }} />
|
||||
<Button
|
||||
title="Unhandled error"
|
||||
style={styles.button}
|
||||
onPress={() => {
|
||||
const err = new Error('some error');
|
||||
throw err;
|
||||
}}
|
||||
/>
|
||||
<View style={{ height: 30 }} />
|
||||
<Button
|
||||
title="Show modal"
|
||||
style={styles.button}
|
||||
onPress={() => {
|
||||
show();
|
||||
}}
|
||||
/>
|
||||
<View style={{ height: 10 }} />
|
||||
</View>
|
||||
<DevTool
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
storageProvider={AsyncStorage}
|
||||
context={global}
|
||||
/>
|
||||
<DevToolModal
|
||||
storageProvider={AsyncStorage}
|
||||
context={global}
|
||||
/>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#efefef',
|
||||
},
|
||||
wrapper: {
|
||||
marginTop: 50,
|
||||
flex: 1,
|
||||
},
|
||||
button: {
|
||||
margin: 100,
|
||||
},
|
||||
});
|
||||
40
packages/demo/CHANGELOG.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-alpha.7](https://github.com/morten-olsen/react-native-debug-console/compare/v2.0.0-alpha.6...v2.0.0-alpha.7) (2019-07-05)
|
||||
|
||||
**Note:** Version bump only for package react-native-debug-console-demo
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-alpha.6](https://github.com/morten-olsen/react-native-debug-console/compare/v2.0.0-alpha.5...v2.0.0-alpha.6) (2019-07-05)
|
||||
|
||||
**Note:** Version bump only for package react-native-debug-console-demo
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-alpha.5](https://github.com/morten-olsen/react-native-debug-console/compare/v2.0.0-alpha.4...v2.0.0-alpha.5) (2019-07-05)
|
||||
|
||||
**Note:** Version bump only for package react-native-debug-console-demo
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-alpha.4](https://github.com/morten-olsen/react-native-debug-console/compare/v2.0.0-alpha.3...v2.0.0-alpha.4) (2019-07-05)
|
||||
|
||||
**Note:** Version bump only for package react-native-debug-console-demo
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-alpha.3](https://github.com/morten-olsen/react-native-debug-console/compare/v2.0.0-alpha.2...v2.0.0-alpha.3) (2019-07-05)
|
||||
|
||||
**Note:** Version bump only for package react-native-debug-console-demo
|
||||
10
packages/demo/Entry.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { registerRootComponent } from 'expo';
|
||||
import { activateKeepAwake } from 'expo-keep-awake';
|
||||
|
||||
import App from './App';
|
||||
|
||||
if (__DEV__) {
|
||||
activateKeepAwake();
|
||||
}
|
||||
|
||||
registerRootComponent(App);
|
||||
34
packages/demo/app.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "react-native-debug-console",
|
||||
"slug": "demo",
|
||||
"privacy": "public",
|
||||
"sdkVersion": "33.0.0",
|
||||
"platforms": [
|
||||
"ios",
|
||||
"android",
|
||||
"web"
|
||||
],
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
},
|
||||
"assetBundlePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"packagerOpts": {
|
||||
"config": "./metro.js",
|
||||
"projectRoots": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
packages/demo/assets/icon.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
packages/demo/assets/splash.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
3
packages/demo/babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const config = require('../../babel.config');
|
||||
|
||||
module.exports = config;
|
||||
12
packages/demo/metro.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const { createMetroConfiguration } = require('expo-yarn-workspaces');
|
||||
const path = require('path');
|
||||
|
||||
//module.exports = createMetroConfiguration(__dirname);
|
||||
|
||||
module.exports = {
|
||||
watchFolders: [
|
||||
__dirname,
|
||||
path.join(__dirname, '..', 'lib'),
|
||||
path.join(__dirname, '..', '..', 'node_modules'),
|
||||
]
|
||||
};
|
||||
30
packages/demo/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "react-native-debug-console-demo",
|
||||
"version": "2.0.0-alpha.7",
|
||||
"main": "./Entry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject",
|
||||
"postinstall": "expo-yarn-workspaces postinstall"
|
||||
},
|
||||
"dependencies": {
|
||||
"expo": "^33.0.0",
|
||||
"react": "16.8.3",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-33.0.0.tar.gz",
|
||||
"react-native-debug-console": "^2.0.0-alpha.7",
|
||||
"react-native-web": "0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/runtime": "^7.5.0",
|
||||
"babel-plugin-module-resolver": "^3.2.0",
|
||||
"babel-preset-expo": "^5.1.1",
|
||||
"expo-cli": "^2.21.2",
|
||||
"expo-yarn-workspaces": "^1.2.0",
|
||||
"react-art": "^16.8.6"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
6
packages/lib/.npmignore
Normal file
@@ -0,0 +1,6 @@
|
||||
/babel.config.js
|
||||
/node_modules
|
||||
/__mocks__
|
||||
/test
|
||||
/src
|
||||
/jest.config.js
|
||||
1
packages/lib/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
@morten-olsen:registry=https://npm.pkg.github.com/
|
||||
40
packages/lib/CHANGELOG.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.0.0-alpha.7](https://github.com/morten-olsen/react-native-debugger/compare/v2.0.0-alpha.6...v2.0.0-alpha.7) (2019-07-05)
|
||||
|
||||
**Note:** Version bump only for package react-native-debug-console
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-alpha.6](https://github.com/morten-olsen/react-native-debugger/compare/v2.0.0-alpha.5...v2.0.0-alpha.6) (2019-07-05)
|
||||
|
||||
**Note:** Version bump only for package react-native-debug-console
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-alpha.5](https://github.com/morten-olsen/react-native-debugger/compare/v2.0.0-alpha.4...v2.0.0-alpha.5) (2019-07-05)
|
||||
|
||||
**Note:** Version bump only for package react-native-debug-console
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-alpha.4](https://github.com/morten-olsen/react-native-debugger/compare/v2.0.0-alpha.3...v2.0.0-alpha.4) (2019-07-05)
|
||||
|
||||
**Note:** Version bump only for package react-native-debug-console
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.0.0-alpha.3](https://github.com/morten-olsen/react-native-debugger/compare/v2.0.0-alpha.2...v2.0.0-alpha.3) (2019-07-05)
|
||||
|
||||
**Note:** Version bump only for package react-native-debug-console
|
||||
2
packages/lib/__mocks__/react.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
const React = require('react')
|
||||
module.exports = { ...React, useEffect: React.useLayoutEffect }
|
||||
3
packages/lib/babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const config = require('../../babel.config');
|
||||
|
||||
module.exports = config;
|
||||
3
packages/lib/jest.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const config = require('../../jest.config');
|
||||
|
||||
module.exports = config;
|
||||
25
packages/lib/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "react-native-debug-console",
|
||||
"version": "2.0.0-alpha.9",
|
||||
"main": "lib/index.js",
|
||||
"react-native": "lib/native.js",
|
||||
"repository": {
|
||||
"url": "https://github.com/morten-olsen/react-native-debugger"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"bundle": "babel --copy-files --extensions '.ts,.tsx,.js' -d lib src",
|
||||
"prepublish": "yarn run bundle"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-native-json-tree": "^1.2.0",
|
||||
"styled-components": "^5.0.0-beta.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prop-types": "^15.6.0",
|
||||
"react": "^16.8.3",
|
||||
"react-native": "^0.59.8",
|
||||
"react-native-webview": "^6.9.0"
|
||||
}
|
||||
}
|
||||
114
packages/lib/src/components/DevTool/Console/Input.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
TextInput,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import styled from 'styled-components/native';
|
||||
import { createContext } from '../../../console';
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const Input = ({
|
||||
provider,
|
||||
context: baseContext,
|
||||
}) => {
|
||||
const [text, setText] = useState('');
|
||||
const [history, setHistory] = useState([]);
|
||||
const [historyOffset, setHistoryOffset] = useState();
|
||||
|
||||
const send = () => {
|
||||
const newHistory = [...history, text];
|
||||
const context = createContext({
|
||||
logProvider: provider,
|
||||
}, baseContext);
|
||||
const contextKeys = Object.keys(context);
|
||||
const contextValues = Object.values(context);
|
||||
const fn = new Function(...contextKeys, text);
|
||||
try {
|
||||
fn(...contextValues);
|
||||
setText('');
|
||||
setHistoryOffset(undefined);
|
||||
setHistory(newHistory);
|
||||
} catch (err) {
|
||||
provider.error([err]);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Button
|
||||
onPress={() => {
|
||||
let currentOffset = typeof historyOffset === 'undefined' ? -1 : historyOffset;
|
||||
currentOffset += 1;
|
||||
const index = history.length - 1 - currentOffset;
|
||||
if (history[index]) {
|
||||
setText(history[index]);
|
||||
setHistoryOffset(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]) {
|
||||
setText(history[index]);
|
||||
setHistoryOffset(currentOffset);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon name="right" />
|
||||
</Button>
|
||||
<TextInput
|
||||
multiline
|
||||
placeholder="{your code here}"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
style={styles.input}
|
||||
value={text}
|
||||
onChangeText={text => setText(text)}
|
||||
onKeyPress={(evt) => {
|
||||
global.proxyConsole.log(Platform.OS === 'web' && evt.key === 'Enter' && evt.shiftKey);
|
||||
if (Platform.OS === 'web' && evt.key === 'Enter' && !evt.shiftKey) {
|
||||
send();
|
||||
return false;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
onPress={() => send()}
|
||||
>
|
||||
<Icon name="play" />
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Input;
|
||||
133
packages/lib/src/components/DevTool/Console/Output.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import React, { Fragment, createRef } from 'react';
|
||||
import styled from 'styled-components/native';
|
||||
import JSONTree from 'react-native-json-tree';
|
||||
import {
|
||||
ScrollView
|
||||
} from 'react-native';
|
||||
import {
|
||||
Body,
|
||||
Emphasis,
|
||||
Fixed,
|
||||
} from '../../base/text';
|
||||
|
||||
const theme = {
|
||||
scheme: 'bright',
|
||||
author: 'chris kempson (http://chriskempson.com)',
|
||||
base00: '#000000',
|
||||
base01: '#303030',
|
||||
base02: '#505050',
|
||||
base03: '#b0b0b0',
|
||||
base04: '#d0d0d0',
|
||||
base05: '#e0e0e0',
|
||||
base06: '#f5f5f5',
|
||||
base07: '#ffffff',
|
||||
base08: '#fb0120',
|
||||
base09: '#fc6d24',
|
||||
base0A: '#fda331',
|
||||
base0B: '#a1c659',
|
||||
base0C: '#76c7b7',
|
||||
base0D: '#6fb3d2',
|
||||
base0E: '#d381c3',
|
||||
base0F: '#be643c'
|
||||
};
|
||||
|
||||
const Wrapper = styled.View``;
|
||||
export const List = styled.View`
|
||||
padding-left: 10px;
|
||||
border-left-width: 10px;
|
||||
border-color: ${props => props.color || 'black' }
|
||||
`;
|
||||
export const Row = styled.View`
|
||||
margin: 10px;
|
||||
`;
|
||||
|
||||
const getColor = (type) => {
|
||||
if (type === 'error') {
|
||||
return 'red';
|
||||
}
|
||||
if (type === 'warn') {
|
||||
return 'yellow';
|
||||
}
|
||||
if (type === 'verbose') {
|
||||
return 'gray';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const formatData = (data, options) => {
|
||||
const {
|
||||
includeStackTrace,
|
||||
} = options;
|
||||
if (typeof data === 'undefined') {
|
||||
return <Fixed>undefined</Fixed>;
|
||||
}
|
||||
if (data instanceof Error) {
|
||||
if (includeStackTrace) {
|
||||
return (
|
||||
<JSONTree
|
||||
theme={theme}
|
||||
data={{
|
||||
message: data.toString(),
|
||||
stackTrace: data.stack ? data.stack.toString() : undefined,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <Fixed selectable={true}>{data.toString()}</Fixed>;
|
||||
}
|
||||
}
|
||||
if (typeof data === 'object') {
|
||||
return <JSONTree data={data} />
|
||||
}
|
||||
return <Fixed selectable={true}>{data.toString()}</Fixed>;
|
||||
}
|
||||
|
||||
const OutputList = ({
|
||||
items,
|
||||
color,
|
||||
includeStackTrace,
|
||||
}) => (
|
||||
<List color={color}>
|
||||
{items.map((data, i) => (
|
||||
<Fragment key={i}>
|
||||
{formatData(data, {
|
||||
includeStackTrace,
|
||||
})}
|
||||
</Fragment>
|
||||
))}
|
||||
</List>
|
||||
)
|
||||
|
||||
const Console = ({
|
||||
logs,
|
||||
includeStackTrace,
|
||||
filter = [],
|
||||
}) => {
|
||||
// const ref = createRef();
|
||||
return (
|
||||
<ScrollView
|
||||
onContentSizeChange={(contentWidth, contentHeight)=>{
|
||||
/*if (ref.current) {
|
||||
ref.current.scrollView.scrollToEnd({animated: true});
|
||||
}*/
|
||||
}}
|
||||
>
|
||||
<Wrapper>
|
||||
{logs.filter(l => filter.includes(l.type)).map((log, i) => (
|
||||
<Row key={i}>
|
||||
<Emphasis color={getColor(log.type)}>
|
||||
{log.type}
|
||||
</Emphasis>
|
||||
<OutputList
|
||||
items={log.data}
|
||||
includeStackTrace={includeStackTrace}
|
||||
color={getColor(log.type)}
|
||||
/>
|
||||
</Row>
|
||||
))}
|
||||
</Wrapper>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default Console;
|
||||
64
packages/lib/src/components/DevTool/Console/index.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import useLog from '../../data/log';
|
||||
import Toolbar, {
|
||||
Button,
|
||||
Selector,
|
||||
Seperator,
|
||||
} from '../../base/Toolbar';
|
||||
import Output from './Output';
|
||||
import Input from './Input';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const initFilters = [
|
||||
'error',
|
||||
'warn',
|
||||
'info',
|
||||
'debug',
|
||||
].map(i => ({
|
||||
name: i,
|
||||
value: i,
|
||||
selected: true,
|
||||
}))
|
||||
|
||||
const Console = ({
|
||||
includeStackTrace,
|
||||
provider,
|
||||
context,
|
||||
}) => {
|
||||
const logs = useLog(provider);
|
||||
const [filters, setFilters] = useState(initFilters);
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Toolbar>
|
||||
<Selector
|
||||
name="Filter"
|
||||
icon="filter"
|
||||
options={filters}
|
||||
multiSelect
|
||||
onSelect={(selected) => {
|
||||
setFilters([...selected]);
|
||||
}}
|
||||
/>
|
||||
<Seperator />
|
||||
<Button
|
||||
name="Clear"
|
||||
icon="trash"
|
||||
onPress={() => provider.clear()}
|
||||
/>
|
||||
</Toolbar>
|
||||
<Output filter={filters.filter(f => f.selected).map(f => f.name)} logs={logs} includeStackTrace={includeStackTrace} />
|
||||
<Input provider={provider} context={context} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Console;
|
||||
94
packages/lib/src/components/DevTool/Console/tools.js
Normal file
@@ -0,0 +1,94 @@
|
||||
|
||||
|
||||
var DEFAULT_MAX_DEPTH = 3;
|
||||
var DEFAULT_ARRAY_MAX_LENGTH = 50;
|
||||
var seen; // Same variable used for all stringifications
|
||||
|
||||
Date.prototype.toPrunedJSON = Date.prototype.toJSON;
|
||||
String.prototype.toPrunedJSON = String.prototype.toJSON;
|
||||
|
||||
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
|
||||
meta = { // table of character substitutions
|
||||
'\b': '\\b',
|
||||
'\t': '\\t',
|
||||
'\n': '\\n',
|
||||
'\f': '\\f',
|
||||
'\r': '\\r',
|
||||
'"' : '\\"',
|
||||
'\\': '\\\\'
|
||||
};
|
||||
|
||||
function quote(string) {
|
||||
escapable.lastIndex = 0;
|
||||
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
|
||||
var c = meta[a];
|
||||
return typeof c === 'string'
|
||||
? c
|
||||
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
||||
}) + '"' : '"' + string + '"';
|
||||
}
|
||||
|
||||
function str(key, holder, depthDecr, arrayMaxLength) {
|
||||
var i, // The loop counter.
|
||||
k, // The member key.
|
||||
v, // The member value.
|
||||
length,
|
||||
partial,
|
||||
value = holder[key];
|
||||
if (value && typeof value === 'object' && typeof value.toPrunedJSON === 'function') {
|
||||
value = value.toPrunedJSON(key);
|
||||
}
|
||||
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
return quote(value);
|
||||
case 'number':
|
||||
return isFinite(value) ? String(value) : 'null';
|
||||
case 'boolean':
|
||||
case 'null':
|
||||
return String(value);
|
||||
case 'object':
|
||||
if (!value) {
|
||||
return 'null';
|
||||
}
|
||||
if (depthDecr<=0 || seen.indexOf(value)!==-1) {
|
||||
return '"-pruned-"';
|
||||
}
|
||||
seen.push(value);
|
||||
partial = [];
|
||||
if (Object.prototype.toString.apply(value) === '[object Array]') {
|
||||
length = Math.min(value.length, arrayMaxLength);
|
||||
for (i = 0; i < length; i += 1) {
|
||||
partial[i] = str(i, value, depthDecr-1, arrayMaxLength) || 'null';
|
||||
}
|
||||
v = partial.length === 0
|
||||
? '[]'
|
||||
: '[' + partial.join(',') + ']';
|
||||
return v;
|
||||
}
|
||||
for (k in value) {
|
||||
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
||||
try {
|
||||
v = str(k, value, depthDecr-1, arrayMaxLength);
|
||||
if (v) partial.push(quote(k) + ':' + v);
|
||||
} catch (e) {
|
||||
// this try/catch due to some "Accessing selectionEnd on an input element that cannot have a selection." on Chrome
|
||||
}
|
||||
}
|
||||
}
|
||||
v = partial.length === 0
|
||||
? '{}'
|
||||
: '{' + partial.join(',') + '}';
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
export default function (value, depthDecr, arrayMaxLength) {
|
||||
seen = [];
|
||||
depthDecr = depthDecr || DEFAULT_MAX_DEPTH;
|
||||
arrayMaxLength = arrayMaxLength || DEFAULT_ARRAY_MAX_LENGTH;
|
||||
const raw = str('', {'': value}, depthDecr, arrayMaxLength);
|
||||
// return JSON.stringify(JSON.parse(raw.root), null, ' ');
|
||||
return raw;
|
||||
};
|
||||
77
packages/lib/src/components/DevTool/Modal.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
SafeAreaView,
|
||||
KeyboardAvoidingView,
|
||||
} from 'react-native';
|
||||
import events from '../../events';
|
||||
import DevTool from './index';
|
||||
import Modal from '../base/Modal';
|
||||
|
||||
|
||||
class Events extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
visible: false,
|
||||
};
|
||||
this.listen = this.listen.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
events.listen(this.listen);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
events.unlisten(this.listen);
|
||||
}
|
||||
|
||||
listen(type, data) {
|
||||
if (type === 'SHOW_DEVTOOLS') {
|
||||
return this.setState({
|
||||
visible: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (type === 'HIDE_DEVTOOLS') {
|
||||
return this.setState({
|
||||
visible: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
...others
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
animationType="slide"
|
||||
transparent={false}
|
||||
visible={this.state.visible}
|
||||
onRequestClose={() => {
|
||||
}}
|
||||
>
|
||||
<SafeAreaView
|
||||
forceInset={{ top: 'always', vertical: 'always', bottom: 'always' }}
|
||||
style={{flex: 1}}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
style={{flex: 1}}
|
||||
behavior="padding"
|
||||
enabled
|
||||
>
|
||||
<DevTool
|
||||
{...others}
|
||||
onClose={() => {
|
||||
events.publish('HIDE_DEVTOOLS');
|
||||
}}
|
||||
/>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Events;
|
||||
149
packages/lib/src/components/DevTool/Requests/Details.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import {
|
||||
ScrollView,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import styled from 'styled-components/native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
import {
|
||||
Emphasis,
|
||||
Fixed,
|
||||
} from '../../base/text';
|
||||
import JSONTree from 'react-native-json-tree';
|
||||
import Cell from '../../base/Cell';
|
||||
import CellHeader from '../../base/CellHeader';
|
||||
import Tab from '../Tab';
|
||||
|
||||
const theme = {
|
||||
scheme: 'bright',
|
||||
author: 'chris kempson (http://chriskempson.com)',
|
||||
base00: '#000000',
|
||||
base01: '#303030',
|
||||
base02: '#505050',
|
||||
base03: '#b0b0b0',
|
||||
base04: '#d0d0d0',
|
||||
base05: '#e0e0e0',
|
||||
base06: '#f5f5f5',
|
||||
base07: '#ffffff',
|
||||
base08: '#fb0120',
|
||||
base09: '#fc6d24',
|
||||
base0A: '#fda331',
|
||||
base0B: '#a1c659',
|
||||
base0C: '#76c7b7',
|
||||
base0D: '#6fb3d2',
|
||||
base0E: '#d381c3',
|
||||
base0F: '#be643c'
|
||||
};
|
||||
|
||||
const Indented = styled.View`
|
||||
margin: 0 25px;
|
||||
`;
|
||||
|
||||
const getResponse = (contentType, request) => {
|
||||
if (!contentType) {
|
||||
return null;
|
||||
}
|
||||
if (request.responseType == 'blob' || request.responseType == 'ArrayBuffer') {
|
||||
return <Emphasis>🤖 Binary</Emphasis>
|
||||
}
|
||||
const contentTypes = contentType.split(';').map(c => c.trim());
|
||||
|
||||
if (contentTypes.includes('application/json')) {
|
||||
const data = JSON.parse(request.responseText);
|
||||
return <JSONTree theme={theme} data={data} />
|
||||
}
|
||||
|
||||
return <Fixed selectable={true}>{request.responseText}</Fixed>;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
const Data = ({
|
||||
url,
|
||||
method,
|
||||
status,
|
||||
headers,
|
||||
requestHeaders,
|
||||
args = [],
|
||||
}) => {
|
||||
const headerInfo = Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\n');
|
||||
return (
|
||||
<ScrollView>
|
||||
<View>
|
||||
<Cell left="Status" right={status} />
|
||||
<Cell left="Method" right={method} />
|
||||
<Cell left="Url" right={url} />
|
||||
<CellHeader>Response Headers</CellHeader>
|
||||
<Indented>
|
||||
<Fixed>
|
||||
{requestHeaders}
|
||||
</Fixed>
|
||||
</Indented>
|
||||
{headerInfo.length > 0 && (
|
||||
<Fragment>
|
||||
<CellHeader>Request Headers</CellHeader>
|
||||
<Indented>
|
||||
<Fixed selectable={true}>
|
||||
{headerInfo}
|
||||
</Fixed>
|
||||
</Indented>
|
||||
</Fragment>
|
||||
)}
|
||||
{args[0] && (
|
||||
<Fragment>
|
||||
<CellHeader>Request Body</CellHeader>
|
||||
<Fixed selectable={true}>{args[0].toString()}</Fixed>
|
||||
</Fragment>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
const Response = ({
|
||||
contentType,
|
||||
request,
|
||||
}) => (
|
||||
<ScrollView>
|
||||
<View>
|
||||
{getResponse(contentType, request)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
|
||||
const getPreview = (contentType, request, url) => {
|
||||
if (!contentType || request.responseType == 'blob' || request.responseType == 'ArrayBuffer' || !contentType) {
|
||||
return [];
|
||||
}
|
||||
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={[{
|
||||
name: 'Details',
|
||||
view: <Data {...props} />
|
||||
}, {
|
||||
name: 'Response',
|
||||
view: <Response {...props} />
|
||||
}, ...getPreview(props.contentType, props.request, props.url)]}
|
||||
/>
|
||||
);
|
||||
|
||||
export default RequestDetails;
|
||||
54
packages/lib/src/components/DevTool/Requests/List.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import styled from 'styled-components/native';
|
||||
import Status from '../../base/Status';
|
||||
import Row from '../../base/Row';
|
||||
import {
|
||||
Body,
|
||||
} from '../../base/text';
|
||||
|
||||
const ScrollView = styled.ScrollView`
|
||||
flex: 1;
|
||||
border-bottom-width: 1px;
|
||||
border-color: #ccc;
|
||||
`;
|
||||
|
||||
const Wrapper = styled.View`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const TouchableOpacity = styled.TouchableOpacity`
|
||||
`;
|
||||
|
||||
const RequestDetails = ({
|
||||
requests,
|
||||
onSelect,
|
||||
selected,
|
||||
}) => (
|
||||
<ScrollView>
|
||||
<Wrapper>
|
||||
{requests.map(({
|
||||
id,
|
||||
status,
|
||||
method,
|
||||
url,
|
||||
}, i) => (
|
||||
<TouchableOpacity
|
||||
key={i}
|
||||
onPress={() => onSelect(i)}
|
||||
>
|
||||
<Row
|
||||
selected={selected === id}
|
||||
left={<Body>{method}</Body>}
|
||||
right={(
|
||||
<Status code={status} />
|
||||
)}
|
||||
>
|
||||
<Body>{url}</Body>
|
||||
</Row>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</Wrapper>
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
export default RequestDetails;
|
||||
55
packages/lib/src/components/DevTool/Requests/index.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import network from '../../../network';
|
||||
import useRequests from '../../data/requests';
|
||||
import Toolbar, {
|
||||
Button,
|
||||
Seperator,
|
||||
} from '../../base/Toolbar';
|
||||
import Details from './Details';
|
||||
import List from './List';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
list: {
|
||||
flex: 1,
|
||||
borderColor: '#ccc',
|
||||
borderBottomWidth: 1,
|
||||
},
|
||||
details: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
let i = 0;
|
||||
const Console = ({
|
||||
provider,
|
||||
}) => {
|
||||
const requests = useRequests(provider);
|
||||
const [active, setActive] = useState();
|
||||
const selected = active >= 0 ? requests[active] : undefined;
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Toolbar>
|
||||
<Seperator />
|
||||
<Button
|
||||
name="Clear"
|
||||
icon="trash"
|
||||
onPress={() => network.clear()}
|
||||
/>
|
||||
</Toolbar>
|
||||
<List
|
||||
selected={selected ? selected.id : undefined}
|
||||
requests={requests}
|
||||
onSelect={(i) => setActive(i)}
|
||||
/>
|
||||
{selected && <Details {...selected} />}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default Console;
|
||||
41
packages/lib/src/components/DevTool/Storage/Keys.js
Normal 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 || '[unknown]'}
|
||||
onPress={() => onSelect(key)}
|
||||
>
|
||||
<Row
|
||||
selected={selected === key}
|
||||
>
|
||||
<Body>{key}</Body>
|
||||
</Row>
|
||||
</Button>
|
||||
))}
|
||||
</Wrapper>
|
||||
</Scroll>
|
||||
)
|
||||
|
||||
export default Keys;
|
||||
27
packages/lib/src/components/DevTool/Storage/Value.js
Normal 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;
|
||||
60
packages/lib/src/components/DevTool/Storage/index.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import React, { useState } from 'react';
|
||||
import styled from 'styled-components/native';
|
||||
import useStorage from '../../data/storage';
|
||||
import Toolbar, {
|
||||
Button,
|
||||
Seperator,
|
||||
} from '../../base/Toolbar';
|
||||
import Keys from './Keys';
|
||||
import Value from './Value';
|
||||
|
||||
const Wrapper = styled.View`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const StorageView = ({
|
||||
provider,
|
||||
}) => {
|
||||
const [selected, setSelected] = useState();
|
||||
const {
|
||||
data,
|
||||
update,
|
||||
removeItem,
|
||||
clear,
|
||||
} = useStorage(provider);
|
||||
return (
|
||||
<Wrapper>
|
||||
<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) => {
|
||||
setSelected(key);
|
||||
}}
|
||||
keys={Object.keys(data)}
|
||||
/>
|
||||
{selected && data[selected] && (
|
||||
<Value value={data[selected]} />
|
||||
)}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default StorageView;
|
||||
118
packages/lib/src/components/DevTool/Tab.js
Normal file
@@ -0,0 +1,118 @@
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import styled from 'styled-components/native';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Icon from '../base/Icon';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
tabs: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderBottomWidth: 1,
|
||||
borderColor: '#ccc',
|
||||
},
|
||||
tabInactive: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 10,
|
||||
},
|
||||
tabActive: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 10,
|
||||
borderBottomWidth: 4,
|
||||
borderColor: '#2980b9',
|
||||
},
|
||||
});
|
||||
|
||||
const Button = styled.TouchableOpacity`
|
||||
padding: 10px 20px 10px 0;
|
||||
`;
|
||||
|
||||
const TabScroll = styled.ScrollView`
|
||||
flex: 1;
|
||||
border-right-width: 1px;
|
||||
border-color: #ccc;
|
||||
margin-right: 20px;
|
||||
`;
|
||||
|
||||
const TabWrapper = styled.View`
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
const Header = styled.View`
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
const Tab = styled.TouchableOpacity`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 20px;
|
||||
flex: 1;
|
||||
`
|
||||
|
||||
const Console = ({
|
||||
tabs,
|
||||
onClose,
|
||||
onDownload,
|
||||
}) => {
|
||||
const [active, setActive] = useState(0);
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Header>
|
||||
<TabScroll horizontal>
|
||||
<TabWrapper>
|
||||
{tabs.map(({ name }, i) => (
|
||||
<Tab
|
||||
key={name}
|
||||
style={active === i ? styles.tabActive : styles.tabInactive}
|
||||
onPress={() => {
|
||||
setActive(i);
|
||||
}}
|
||||
>
|
||||
<Text>{name}</Text>
|
||||
</Tab>
|
||||
))}
|
||||
</TabWrapper>
|
||||
</TabScroll>
|
||||
{onDownload && (
|
||||
<Button
|
||||
onPress={onDownload}
|
||||
>
|
||||
<Icon name="download" />
|
||||
</Button>
|
||||
)}
|
||||
{onClose && (
|
||||
<Button
|
||||
onPress={onClose}
|
||||
>
|
||||
<Icon name="close" />
|
||||
</Button>
|
||||
)}
|
||||
</Header>
|
||||
{tabs[active] && tabs[active].view}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
Console.propTypes = {
|
||||
tabs: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
})),
|
||||
};
|
||||
|
||||
Console.defaultProps = {
|
||||
tabs: [],
|
||||
};
|
||||
|
||||
export default Console;
|
||||
93
packages/lib/src/components/DevTool/index.js
Normal file
@@ -0,0 +1,93 @@
|
||||
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: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const DevTool = ({
|
||||
style,
|
||||
includeStackTrace,
|
||||
onClose,
|
||||
logProvider = log,
|
||||
storageProvider,
|
||||
context = {},
|
||||
requestProvider = network,
|
||||
additionalTools = [],
|
||||
}) => {
|
||||
const views = [];
|
||||
|
||||
if (logProvider) {
|
||||
views.push({
|
||||
name: 'Console',
|
||||
view: (
|
||||
<Console
|
||||
includeStackTrace={includeStackTrace}
|
||||
provider={logProvider}
|
||||
context={context}
|
||||
/>
|
||||
),
|
||||
getData: log.get,
|
||||
});
|
||||
}
|
||||
|
||||
if (requestProvider) {
|
||||
views.push({
|
||||
name: 'Network',
|
||||
view: <Requests provider={requestProvider} />,
|
||||
getData: network.get,
|
||||
});
|
||||
}
|
||||
|
||||
if (storageProvider) {
|
||||
views.push({
|
||||
name: 'Storage',
|
||||
view: <Storage provider={storageProvider} />,
|
||||
});
|
||||
}
|
||||
|
||||
additionalTools.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;
|
||||
29
packages/lib/src/components/base/Cell.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components/native';
|
||||
import {
|
||||
Body,
|
||||
Emphasis,
|
||||
} from './text';
|
||||
|
||||
const Wrapper = styled.View`
|
||||
flex-direction: row;
|
||||
padding: 10px;
|
||||
`;
|
||||
|
||||
const Left = styled.View`
|
||||
width: 100px;
|
||||
`;
|
||||
|
||||
const Row = ({
|
||||
left,
|
||||
right,
|
||||
}) => (
|
||||
<Wrapper>
|
||||
<Left>
|
||||
<Emphasis>{left}:</Emphasis>
|
||||
</Left>
|
||||
<Body selectable={true}>{right}</Body>
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
export default Row;
|
||||
20
packages/lib/src/components/base/CellHeader.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components/native';
|
||||
import {
|
||||
Emphasis,
|
||||
} from './text';
|
||||
|
||||
const Wrapper = styled.View`
|
||||
flex-direction: row;
|
||||
padding: 10px;
|
||||
`;
|
||||
|
||||
const Row = ({
|
||||
children,
|
||||
}) => (
|
||||
<Wrapper>
|
||||
<Emphasis>{children}:</Emphasis>
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
export default Row;
|
||||
BIN
packages/lib/src/components/base/Icon/check.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
packages/lib/src/components/base/Icon/close.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
packages/lib/src/components/base/Icon/download.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
packages/lib/src/components/base/Icon/filter.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
47
packages/lib/src/components/base/Icon/index.js
Normal file
@@ -0,0 +1,47 @@
|
||||
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';
|
||||
import filter from './filter.png';
|
||||
import square from './square.png';
|
||||
import check from './check.png';
|
||||
|
||||
const icons = {
|
||||
reload,
|
||||
trash,
|
||||
remove,
|
||||
right,
|
||||
left,
|
||||
play,
|
||||
download,
|
||||
close,
|
||||
filter,
|
||||
square,
|
||||
check,
|
||||
}
|
||||
|
||||
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;
|
||||
BIN
packages/lib/src/components/base/Icon/left.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
packages/lib/src/components/base/Icon/play.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
packages/lib/src/components/base/Icon/reload.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
packages/lib/src/components/base/Icon/return.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
packages/lib/src/components/base/Icon/right.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
packages/lib/src/components/base/Icon/square.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
packages/lib/src/components/base/Icon/trash.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
5
packages/lib/src/components/base/Modal/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import {
|
||||
Modal
|
||||
} from 'react-native';
|
||||
|
||||
export default Modal;
|
||||
41
packages/lib/src/components/base/Modal/index.web.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: fixed;
|
||||
background: #fff;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const Modal = ({
|
||||
visible,
|
||||
children
|
||||
}) => {
|
||||
const [root, setRoot] = useState();
|
||||
useEffect(() => {
|
||||
const elm = document.createElement('div');
|
||||
document.body.appendChild(elm);
|
||||
setRoot(elm);
|
||||
}, []);
|
||||
|
||||
const elm = visible ? (
|
||||
<Wrapper>{children}</Wrapper>
|
||||
) : null;
|
||||
|
||||
if (!root) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
elm,
|
||||
root,
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
37
packages/lib/src/components/base/Row.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components/native';
|
||||
|
||||
const Wrapper = styled.View`
|
||||
flex-direction: row;
|
||||
padding: 10px;
|
||||
border-left-width: ${props => props.selected ? '10px' : '0'};
|
||||
border-left-color: #2980b9;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #efefef;
|
||||
`;
|
||||
|
||||
const Left = styled.View`
|
||||
`;
|
||||
|
||||
const Right = styled.View`
|
||||
`;
|
||||
|
||||
const Center = styled.View`
|
||||
flex: 1;
|
||||
margin: 0 10px;
|
||||
`;
|
||||
|
||||
const Row = ({
|
||||
left,
|
||||
right,
|
||||
children,
|
||||
selected,
|
||||
}) => (
|
||||
<Wrapper selected={selected}>
|
||||
{left && <Left>{left}</Left>}
|
||||
<Center>{children}</Center>
|
||||
{right && <Right>{right}</Right>}
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
export default Row;
|
||||
44
packages/lib/src/components/base/Status.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components/native';
|
||||
import {
|
||||
Body,
|
||||
} from '../base/text';
|
||||
|
||||
const getColor = (code) => {
|
||||
if (code === 'Error') {
|
||||
return '#c0392b';
|
||||
}
|
||||
if (code >= 500) {
|
||||
return '#c0392b';
|
||||
}
|
||||
if (code >= 400) {
|
||||
return '#f1c40f';
|
||||
}
|
||||
if (code >= 300) {
|
||||
return '#2980b9';
|
||||
}
|
||||
return '#2ecc71';
|
||||
}
|
||||
|
||||
const Wrapper = styled.View`
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
const Icon = styled.View`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
background: ${props => getColor(props.code)};
|
||||
margin-left: 5px;
|
||||
`;
|
||||
|
||||
const Status = ({
|
||||
code,
|
||||
}) => (
|
||||
<Wrapper>
|
||||
<Body>{code}</Body>
|
||||
{code !== 'Waiting' && <Icon code={code} />}
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
export default Status;
|
||||
31
packages/lib/src/components/base/Toolbar/Button.js
Normal 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;
|
||||
74
packages/lib/src/components/base/Toolbar/Selector.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import {
|
||||
SafeAreaView,
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import Button from './Button';
|
||||
import Row from '../Row';
|
||||
import Icon from '../Icon';
|
||||
import Modal from '../Modal';
|
||||
import {
|
||||
Body,
|
||||
} from '../text';
|
||||
|
||||
const Selector = ({
|
||||
multiSelect = false,
|
||||
onSelect,
|
||||
options = [],
|
||||
...others
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<Fragment>
|
||||
<Button
|
||||
{...others}
|
||||
onPress={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
/>
|
||||
<Modal
|
||||
animationType="slide"
|
||||
transparent={false}
|
||||
visible={!!open}
|
||||
onRequestClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<SafeAreaView
|
||||
forceInset={{ top: 'always', vertical: 'always' }}
|
||||
style={{flex: 1}}
|
||||
>
|
||||
<Button
|
||||
name="Close"
|
||||
icon="close"
|
||||
onPress={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
{options.map((option) => (
|
||||
<TouchableOpacity
|
||||
key={option.name}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default Selector;
|
||||
7
packages/lib/src/components/base/Toolbar/Seperator.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components/native';
|
||||
|
||||
const Seperator = styled.View`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export default Seperator;
|
||||
29
packages/lib/src/components/base/Toolbar/index.js
Normal 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;
|
||||
14
packages/lib/src/components/base/text.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import styled from 'styled-components/native';
|
||||
|
||||
export const Body = styled.Text`
|
||||
color: ${props => props.color || 'black'};
|
||||
`;
|
||||
|
||||
export const Emphasis = styled.Text`
|
||||
font-weight: bold;
|
||||
color: ${props => props.color || 'black'};
|
||||
`;
|
||||
|
||||
export const Fixed = styled.Text`
|
||||
font-family: Menlo-Regular;
|
||||
`;
|
||||
18
packages/lib/src/components/data/log.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const useLog = (provider) => {
|
||||
const [logs, setLogs] = useState([]);
|
||||
useEffect(() => {
|
||||
const update = (newLogs) => {
|
||||
setLogs([...newLogs]);
|
||||
}
|
||||
provider.listen(update);
|
||||
|
||||
return () => {
|
||||
provider.unlisten(update);
|
||||
}
|
||||
}, []);
|
||||
return logs;
|
||||
};
|
||||
|
||||
export default useLog;
|
||||
18
packages/lib/src/components/data/requests.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
const useRequests = (provider) => {
|
||||
const [requests, setRequests] = useState([]);
|
||||
useEffect(() => {
|
||||
const update = (newRequests) => {
|
||||
setRequests(newRequests);
|
||||
};
|
||||
|
||||
provider.listen(update);
|
||||
return () => {
|
||||
provider.unlisten(update);
|
||||
};
|
||||
}, []);
|
||||
return requests;
|
||||
};
|
||||
|
||||
export default useRequests;
|
||||
40
packages/lib/src/components/data/storage.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
const useStorage = (provider) => {
|
||||
const [data, setData] = useState({});
|
||||
|
||||
const update = async () => {
|
||||
const keys = await provider.getAllKeys();
|
||||
const values = await Promise.all(keys.map(key => provider.getItem(key)));
|
||||
const data = {};
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const value = values[i];
|
||||
data[key] = value;
|
||||
}
|
||||
setData(data);
|
||||
};
|
||||
|
||||
const removeItem = async (name) => {
|
||||
await provider.removeItem(name);
|
||||
await update();
|
||||
}
|
||||
|
||||
const clear = async () => {
|
||||
await provider.clear();
|
||||
await update();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
update().catch(err => console.error(err));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data,
|
||||
update,
|
||||
removeItem,
|
||||
clear,
|
||||
}
|
||||
}
|
||||
|
||||
export default useStorage;
|
||||
9
packages/lib/src/console.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const createContext = ({
|
||||
logProvider,
|
||||
}, baseContext = {}) => ({
|
||||
log: (...args) => logProvider.log(...args),
|
||||
clear: logProvider.clear,
|
||||
window: baseContext,
|
||||
global: baseContext,
|
||||
context: baseContext,
|
||||
});
|
||||
21
packages/lib/src/events.js
Normal file
@@ -0,0 +1,21 @@
|
||||
class Events {
|
||||
constructor() {
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
listen(fn) {
|
||||
this.listeners.push(fn);
|
||||
}
|
||||
|
||||
unlisten(fn) {
|
||||
this.listeners = this.listeners(l => l !== fn);
|
||||
}
|
||||
|
||||
publish(type, data) {
|
||||
this.listeners.forEach(l => l(type, data));
|
||||
}
|
||||
}
|
||||
|
||||
const events = new Events();
|
||||
|
||||
export default events;
|
||||
17
packages/lib/src/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import DevTool from './components/DevTool/index';
|
||||
import DevToolModal from './components/DevTool/Modal';
|
||||
import log from './log';
|
||||
import network from './network';
|
||||
import events from './events';
|
||||
|
||||
const show = () => events.publish('SHOW_DEVTOOLS');
|
||||
const hide = () => events.publish('HIDE_DEVTOOLS');
|
||||
|
||||
export {
|
||||
DevTool,
|
||||
DevToolModal,
|
||||
log,
|
||||
network,
|
||||
show,
|
||||
hide,
|
||||
};
|
||||
104
packages/lib/src/log.js
Normal file
@@ -0,0 +1,104 @@
|
||||
export const proxyConsole = global.console;
|
||||
|
||||
class Log {
|
||||
constructor() {
|
||||
this.logs = [];
|
||||
this.listeners = [];
|
||||
this.clear = this.clear.bind(this);
|
||||
this.get = this.get.bind(this);
|
||||
this.handleError = this.handleError.bind(this);
|
||||
}
|
||||
|
||||
listen(fn) {
|
||||
this.listeners.push(fn);
|
||||
fn(this.logs);
|
||||
}
|
||||
|
||||
unlisten(fn) {
|
||||
this.listeners = this.listeners.filter(l => l !== fn);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.logs = [];
|
||||
this.listeners.forEach(l => l(this.logs));
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.logs;
|
||||
}
|
||||
|
||||
log(type, data, keep) {
|
||||
if (data && data[0] && data[0].indexOf && data[0].indexOf('Warning: Using the "className" prop on') === 0) {
|
||||
return;
|
||||
}
|
||||
const entry = {
|
||||
type,
|
||||
data,
|
||||
};
|
||||
this.logs.push(entry);
|
||||
this.listeners.forEach(l => l(this.logs));
|
||||
if (keep) {
|
||||
proxyConsole[type](...data);
|
||||
}
|
||||
}
|
||||
|
||||
info(data, keep) {
|
||||
this.log('info', data, keep);
|
||||
}
|
||||
|
||||
error(data, keep) {
|
||||
this.log('error', data, keep);
|
||||
}
|
||||
|
||||
warn(data, keep) {
|
||||
this.log('warn', data, keep);
|
||||
}
|
||||
|
||||
debug(data, keep) {
|
||||
this.log('debug', data, keep);
|
||||
}
|
||||
|
||||
handleError(err) {
|
||||
if (err && err.error) {
|
||||
this.error([err.error]);
|
||||
}
|
||||
}
|
||||
|
||||
attach(keep) {
|
||||
const redirected = Object.keys(proxyConsole).reduce((output, key) => ({
|
||||
...output,
|
||||
[key]: keep ? (...args) => proxyConsole[key](...args) : () => {},
|
||||
}), {});
|
||||
global.console = {
|
||||
...redirected,
|
||||
error: (...data) => this.error(data, keep),
|
||||
warn: (...data) => this.warn(data, keep),
|
||||
info: (...data) => this.info(data, keep),
|
||||
log: (...data) => this.info(data, keep),
|
||||
debug: (...data) => this.debug(data, keep),
|
||||
verbose: (...data) => this.debug(data, keep),
|
||||
};
|
||||
/*if (global.ErrorUtils) {
|
||||
global.ErrorUtils.setGlobalHandler((err, fatal) => {
|
||||
this.error([err], keep);
|
||||
});
|
||||
}*/
|
||||
if (global.addEventListener) {
|
||||
global.addEventListener('error', this.handleError);
|
||||
}
|
||||
}
|
||||
|
||||
detach() {
|
||||
overrides.forEach((key) => {
|
||||
window.console[key] = proxies[key];
|
||||
});
|
||||
|
||||
if (global.removeEventListener) {
|
||||
global.removeEventListener('error', this.handleError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const log = new Log();
|
||||
|
||||
export default log;
|
||||
17
packages/lib/src/native.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import DevTool from './components/DevTool/index';
|
||||
import DevToolModal from './components/DevTool/Modal';
|
||||
import log from './log';
|
||||
import network from './network';
|
||||
import events from './events';
|
||||
|
||||
const show = () => events.publish('SHOW_DEVTOOLS');
|
||||
const hide = () => events.publish('HIDE_DEVTOOLS');
|
||||
|
||||
export {
|
||||
DevTool,
|
||||
DevToolModal,
|
||||
log,
|
||||
network,
|
||||
show,
|
||||
hide,
|
||||
};
|
||||
109
packages/lib/src/network.js
Normal file
@@ -0,0 +1,109 @@
|
||||
const proxied = global.XMLHttpRequest ? global.XMLHttpRequest.prototype.open : () => {};
|
||||
let currentId = 0;
|
||||
|
||||
class Network {
|
||||
constructor() {
|
||||
this.requests = [];
|
||||
this.listeners = [];
|
||||
// this.clear = this.clear.bind(this);
|
||||
this.get = this.get.bind(this);
|
||||
}
|
||||
|
||||
listen(fn) {
|
||||
this.listeners.push(fn);
|
||||
fn(this.requests);
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.requests;
|
||||
}
|
||||
|
||||
unlisten(fn) {
|
||||
this.listeners = this.listeners.filter(l => l !== fn);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.requests = [];
|
||||
this.listeners.forEach(l => l(this.requests));
|
||||
}
|
||||
|
||||
addRequest(id, request) {
|
||||
const index = this.requests.findIndex(req => req.id === id);
|
||||
if (index >= 0) {
|
||||
this.requests[index] = {
|
||||
id,
|
||||
...request,
|
||||
};
|
||||
} else {
|
||||
this.requests.push({
|
||||
id,
|
||||
...request,
|
||||
});
|
||||
}
|
||||
this.listeners.forEach(l => l(this.requests));
|
||||
}
|
||||
|
||||
attach() {
|
||||
const me = this;
|
||||
const headers = {};
|
||||
global.XMLHttpRequest.prototype.open = function proxyOpen (...args) {
|
||||
let sendArgs;
|
||||
const [
|
||||
method,
|
||||
url,
|
||||
] = args;
|
||||
const id = currentId++;
|
||||
this.addEventListener('load', () => {
|
||||
me.addRequest(id, {
|
||||
url,
|
||||
method,
|
||||
args: sendArgs,
|
||||
headers,
|
||||
requestHeaders: this.getAllResponseHeaders(),
|
||||
contentType: (this.getResponseHeader('content-type') || ''),
|
||||
request: this,
|
||||
status: this.status || null,
|
||||
});
|
||||
})
|
||||
this.addEventListener('error', (error) => {
|
||||
me.addRequest(id, {
|
||||
url,
|
||||
method,
|
||||
error,
|
||||
args: sendArgs,
|
||||
headers,
|
||||
requestHeaders: this.getAllResponseHeaders(),
|
||||
request: this,
|
||||
status: this.status || 'Error',
|
||||
});
|
||||
})
|
||||
const proxiedSend = this.send;
|
||||
const proxiedSetRequestHeader = this.setRequestHeader;
|
||||
this.send = function proxySend (...sendargs) {
|
||||
sendArgs = sendargs;
|
||||
me.addRequest(id, {
|
||||
url,
|
||||
method,
|
||||
args: sendArgs,
|
||||
headers,
|
||||
request: this,
|
||||
status: 'Waiting',
|
||||
});
|
||||
return proxiedSend.apply(this, [].slice.call(arguments));
|
||||
}
|
||||
this.setRequestHeader = function (name, value) {
|
||||
headers[name] = value;
|
||||
return proxiedSetRequestHeader.apply(this, [].slice.call(arguments));
|
||||
}
|
||||
return proxied.apply(this, [].slice.call(arguments));
|
||||
};
|
||||
}
|
||||
|
||||
detach() {
|
||||
global.XMLHttpRequest.prototype.open = proxied;
|
||||
}
|
||||
}
|
||||
|
||||
const network = new Network();
|
||||
|
||||
export default network;
|
||||
47
packages/lib/test/Console.spec.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import getDevTools from './getDevTools';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
describe('Console', () => {
|
||||
describe('output', () => {
|
||||
let devTool;
|
||||
|
||||
beforeEach(() => {
|
||||
devTool = getDevTools();
|
||||
});
|
||||
|
||||
it('should render an emptry list', () => {
|
||||
expect(devTool.console.output().props.logs).toEqual([
|
||||
]);
|
||||
});
|
||||
|
||||
it('should render one item', () => {
|
||||
renderer.act(() => {
|
||||
devTool.log.add('test');
|
||||
});
|
||||
expect(devTool.console.output().props.logs).toEqual([
|
||||
'test',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should render multible items item', () => {
|
||||
renderer.act(() => {
|
||||
devTool.log.add('1');
|
||||
devTool.log.add('2');
|
||||
devTool.log.add('3');
|
||||
devTool.log.add('4');
|
||||
devTool.log.add('5');
|
||||
devTool.log.add('6');
|
||||
devTool.log.add('7');
|
||||
});
|
||||
expect(devTool.console.output().props.logs).toEqual([
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'7',
|
||||
]);
|
||||
});
|
||||
});
|
||||
})
|
||||
61
packages/lib/test/getDevTools.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import {
|
||||
DevTool,
|
||||
} from '../src';
|
||||
import Console from '../src/components/DevTool/Console';
|
||||
import ConsoleOutput, { Row as OutputRow} from '../src/components/DevTool/Console/Output';
|
||||
import ConsoleInput from '../src/components/DevTool/Console/Input';
|
||||
|
||||
import Requests from '../src/components/DevTool/Requests';
|
||||
import RequestsDetails from '../src/components/DevTool/Requests/Details';
|
||||
import RequestsList from '../src/components/DevTool/Requests/List';
|
||||
|
||||
import Storage from '../src/components/DevTool/Storage';
|
||||
import StorageKeys from '../src/components/DevTool/Storage/Keys';
|
||||
import StorageValues from '../src/components/DevTool/Storage/Value';
|
||||
|
||||
const createLog = () => {
|
||||
const listeners = [];
|
||||
const logs = [];
|
||||
return {
|
||||
listen: (l) => listeners.push(l),
|
||||
unlisten: (l) => listeners = listeners.filter(nl => nl !== l),
|
||||
add: (log) => {
|
||||
logs.push(log);
|
||||
listeners.forEach(l => l(logs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getDevTools = () => {
|
||||
const log = createLog();
|
||||
const instance = renderer.create(
|
||||
<DevTool
|
||||
logProvider={log}
|
||||
/>
|
||||
);
|
||||
const result = {
|
||||
instance,
|
||||
console: {
|
||||
main: () => instance.root.findByType(Console),
|
||||
output: () => instance.root.findByType(ConsoleOutput),
|
||||
rows: () => instance.root.findAllByType(OutputRow),
|
||||
input: () => instance.root.findByType(ConsoleInput),
|
||||
},
|
||||
requests: {
|
||||
main: () => instance.root.findByType(Requests),
|
||||
output: () => instance.root.findByType(RequestsDetails),
|
||||
input: () => instance.root.findByType(RequestsList),
|
||||
},
|
||||
storage: {
|
||||
main: () => instance.root.findByType(Storage),
|
||||
output: () => instance.root.findByType(StorageKeys),
|
||||
input: () => instance.root.findByType(StorageValues),
|
||||
},
|
||||
log,
|
||||
};
|
||||
return result;
|
||||
};
|
||||
|
||||
export default getDevTools;
|
||||