mirror of
https://github.com/morten-olsen/react-native-debug-console.git
synced 2026-02-08 00:36:26 +01:00
V2 (#1)
This commit is contained in:
114
packages/lib/src/components/DevTool/Console/Input.js
Normal file
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
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
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
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;
|
||||
};
|
||||
Reference in New Issue
Block a user