Update the Console dapp (#5602)

* Init Console Dapp structure

* Watches and status

* First REPL and display

* Attaching console

* Selectable autocomplete

* working console // Display objects nicely

* Multilines in Console Dapps

* Better UI

* Eval on window object

* Save console gistory

* Adding views

* Add settings to the console dapp

* Add / remove Watches

* Add Snippets Cmponent

* Semi Working Snippets

* Working Snippets

* Adding CodeMirror features

* Removing old Console

* Add Static folder
This commit is contained in:
Nicolas Gotchac 2017-05-19 17:07:59 +02:00 committed by Gav Wood
parent 3ff72794e5
commit 84cab18120
43 changed files with 3189 additions and 993 deletions

View File

@ -196,11 +196,13 @@
"react": "15.4.2",
"react-ace": "4.1.0",
"react-addons-css-transition-group": "15.4.2",
"react-codemirror": "^0.3.0",
"react-copy-to-clipboard": "4.2.3",
"react-dom": "15.4.2",
"react-dropzone": "3.7.3",
"react-element-to-jsx-string": "6.0.0",
"react-event-listener": "0.4.1",
"react-inspector": "paritytech/react-inspector",
"react-intl": "2.1.5",
"react-markdown": "2.4.4",
"react-portal": "3.0.0",

59
js/src/dapps/console.js Normal file
View File

@ -0,0 +1,59 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import 'codemirror/addon/dialog/dialog';
import 'codemirror/addon/dialog/dialog.css';
import 'codemirror/addon/hint/javascript-hint';
import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/addon/search/match-highlighter';
import 'codemirror/addon/search/search';
import 'codemirror/addon/search/searchcursor';
import 'codemirror/keymap/sublime';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/javascript/javascript';
// Custom codemirror style
import './console/codemirror.css';
import Application from './console/Application';
import '../../assets/fonts/Roboto/font.css';
import '../../assets/fonts/RobotoMono/font.css';
import './style.css';
ReactDOM.render(
<AppContainer>
<Application />
</AppContainer>,
document.querySelector('#container')
);
if (module.hot) {
module.hot.accept('./console/Application/index.js', () => {
require('./console/Application/index.js');
ReactDOM.render(
<AppContainer>
<Application />
</AppContainer>,
document.querySelector('#container')
);
});
}

View File

@ -0,0 +1,65 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.app {
display: flex;
flex-direction: column;
font-family: Arial, sans-serif;
font-size: 11px;
height: 100vh;
overflow: hidden;
}
textarea,
input {
font-family: dejavu sans mono, monospace;
outline: none;
}
code,
pre {
font-family: dejavu sans mono, monospace;
font-size: 11px;
}
.header {
flex: 0 0 auto;
}
.view {
display: flex;
flex: 1;
flex-direction: column;
}
.eval {
flex: 0 1 auto;
font-family: dejavu sans mono, monospace;
overflow: auto;
}
.input {
border-top: 1px solid #eee;
display: flex;
flex: 1 1 auto;
min-height: 50px;
}
.status {
flex: 0 0 auto;
font-family: dejavu sans mono, monospace;
}

View File

@ -0,0 +1,94 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import { api } from '../parity';
import Console from '../Console';
import Header from '../Header';
import Input from '../Input';
import Settings from '../Settings';
import Snippets from '../Snippets';
import Watches from '../Watches';
import ApplicationStore from './application.store';
import WatchesStore from '../Watches/watches.store';
import styles from './application.css';
@observer
export default class Application extends Component {
application = ApplicationStore.get();
watches = WatchesStore.get();
componentWillMount () {
this.watches.add('time', () => new Date());
this.watches.add('blockNumber', api.eth.blockNumber, api);
}
render () {
return (
<div className={ styles.app }>
<div className={ styles.header }>
<Header />
</div>
{ this.renderView() }
<div className={ styles.status }>
<Watches />
</div>
</div>
);
}
renderView () {
const { view } = this.application;
if (view === 'console') {
return (
<div className={ styles.view }>
<div className={ styles.eval }>
<Console />
</div>
<div className={ styles.input }>
<Input />
</div>
</div>
);
}
if (view === 'settings') {
return (
<div className={ styles.view }>
<Settings />
</div>
);
}
if (view === 'snippets') {
return (
<div className={ styles.view }>
<Snippets />
</div>
);
}
return null;
}
}

View File

@ -0,0 +1,42 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
let instance;
export default class ApplicationStore {
@observable view = this.views[0].id;
views = [
{ label: 'Console', id: 'console' },
{ label: 'Snippets', id: 'snippets' },
{ label: 'Settings', id: 'settings' }
];
static get () {
if (!instance) {
instance = new ApplicationStore();
}
return instance;
}
@action
setView (view) {
this.view = view;
}
}

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './application';

View File

@ -0,0 +1,55 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.container {
background: #f8f8f8;
box-shadow: 0 0.125em 0.25em rgba(0, 0, 0, 0.5);
font-family: dejavu sans mono, monospace;
left: 20px;
position: absolute;
max-height: 300px;
overflow: auto;
}
.item {
background-color: white;
padding: 0.25em 0.25em 0.25em 0.35em;
display: flex;
justify-content: space-between;
&.selected {
background-color: rgb(64, 115, 244);
&,
.proto {
color: white;
}
}
&:hover {
cursor: default;
}
&:hover:not(.selected) {
background-color: rgb(230, 236, 255);
}
.proto {
color: gray;
margin-left: 1em;
}
}

View File

@ -0,0 +1,96 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import AutocompleteStore from './autocomplete.store';
import styles from './autocomplete.css';
@observer
export default class Autocomplete extends Component {
autocompleteStore = AutocompleteStore.get();
render () {
if (!this.autocompleteStore.show) {
return null;
}
return (
<div
className={ styles.container }
style={ this.autocompleteStore.position }
>
{ this.renderAutocompletes() }
</div>
);
}
renderAutocompletes () {
const { selected, values } = this.autocompleteStore;
const displayedProto = {};
return values.map((autocomplete, index) => {
const { name, prototypeName } = autocomplete;
const onClick = () => this.handleClick(index);
const setRef = (node) => this.setRef(index, node);
const proto = !displayedProto[prototypeName]
? (
<span className={ styles.proto }>
{ prototypeName }
</span>
)
: null;
if (!displayedProto[prototypeName]) {
displayedProto[prototypeName] = true;
}
const classes = [ styles.item ];
if (index === selected) {
classes.push(styles.selected);
}
return (
<div
className={ classes.join(' ') }
key={ index }
onClick={ onClick }
ref={ setRef }
>
<span>
{ name }
</span>
{ proto }
</div>
);
});
}
handleClick = (index) => {
this.autocompleteStore.select(index);
};
setRef = (index, node) => {
const element = ReactDOM.findDOMNode(node);
this.autocompleteStore.setElement(index, element);
};
}

View File

@ -0,0 +1,234 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
import { evaluate } from '../utils';
let instance;
export default class AutocompleteStore {
@observable values = [];
@observable position = {};
@observable show = false;
@observable selected = null;
elements = {};
inputNode = null;
lastObject = null;
lastObjectPropertyNames = [];
static get () {
if (!instance) {
instance = new AutocompleteStore();
}
return instance;
}
get hasSelected () {
return this.selected !== null;
}
clearCache () {
this.lastObject = null;
this.lastObjectPropertyNames = null;
}
@action
focus (offset = 1) {
if (this.values.length === 0) {
this.selected = null;
return;
}
this.selected = this.selected === null
? (
offset === 1
? 0
: this.values.length - 1
)
: (this.values.length + this.selected + offset) % (this.values.length);
if (this.isVisible(this.selected)) {
return;
}
const element = this.elements[this.selected];
if (!element) {
return;
}
element.scrollIntoView(offset === -1);
}
focusOnInput () {
if (!this.inputNode) {
return;
}
this.inputNode.focus();
}
@action
hide () {
this.show = false;
this.selected = null;
}
isVisible (index) {
const element = this.elements[index];
if (!element) {
return false;
}
const eBoundings = element.getBoundingClientRect();
const pBoundings = element.parentElement.getBoundingClientRect();
if (eBoundings.top < pBoundings.top || eBoundings.bottom > pBoundings.bottom) {
return false;
}
return true;
}
select (inputStore, _index = this.selected) {
const index = _index === null
? 0
: _index;
if (!this.values[index]) {
console.warn(`autocomplete::select has been called on AutocompleteStore with wrong value ${index}`);
return;
}
const { name } = this.values[index];
const { input } = inputStore;
const objects = input.split('.');
objects[objects.length - 1] = name;
const nextInput = objects.join('.');
this.hide();
this.focusOnInput();
return inputStore.updateInput(nextInput, false);
}
setElement (index, element) {
this.elements[index] = element;
}
setInputNode (node) {
this.inputNode = node;
}
@action
setPosition () {
if (!this.inputNode) {
return;
}
const inputBoundings = this.inputNode.getBoundingClientRect();
const bodyBoundings = document.body.getBoundingClientRect();
// display on bottom of input
if (inputBoundings.top < bodyBoundings.height / 2) {
const nextPosition = {
top: 20
};
this.position = nextPosition;
return;
}
// display on top of input
const nextPosition = {
bottom: inputBoundings.height
};
this.position = nextPosition;
return;
}
@action
setValues (values) {
this.values = values;
this.selected = null;
const show = values.length > 0;
// Reveal autocomplete
if (!this.show && show) {
this.setPosition();
}
this.show = show;
}
update (input) {
if (input.length === 0) {
return this.setValues([]);
}
const objects = input.split('.');
const suffix = objects.pop().toLowerCase();
const prefix = objects.join('.');
const object = prefix.length > 0
? prefix
: 'window';
if (object !== this.lastObject) {
const evalResult = evaluate(object);
if (evalResult.error) {
this.lastObjectProperties = [];
} else {
this.lastObjectProperties = getAllProperties(evalResult.result);
}
this.lastObject = object;
}
const autocompletes = this.lastObjectProperties.filter((property) => {
return property.name.toLowerCase().includes(suffix);
});
return this.setValues(autocompletes);
}
}
function getAllProperties (object) {
const propertyNames = {};
while (object) {
const prototypeName = object && object.constructor && object.constructor.name || '';
Object.getOwnPropertyNames(object)
.sort()
.forEach((name) => {
if (Object.prototype.hasOwnProperty.call(propertyNames, name)) {
return;
}
propertyNames[name] = { name, prototypeName };
});
object = Object.getPrototypeOf(object);
}
return Object.values(propertyNames);
}

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './autocomplete';

View File

@ -0,0 +1,58 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.result {
border-top: 1px solid #eee;
display: flex;
font-family: dejavu sans mono, monospace;
padding: 0.35em 0.25em;
&.error {
background-color: hsl(0, 100%, 97%);
.text {
color: red;
}
}
&.warn {
background-color: hsl(50, 100%, 95%);
}
}
.type {
font-weight: bold !important;
font-size: 8pt;
padding: 0 0.5em 0 0.25em;
}
.time {
color: gray;
padding: 0 1em 0 0.5em;
}
.token {
white-space: pre-wrap;
}
.text {
display: flex;
}
.text .token:not(:first-child) {
margin-left: 0.5em;
}

View File

@ -0,0 +1,118 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { ObjectInspector } from 'react-inspector';
import ConsoleStore from './console.store';
import SettingsStore from '../Settings/settings.store';
import styles from './console.css';
const ICONS = {
debug: '&nbsp;',
error: '✖',
info: '',
input: '&gt;',
log: '&nbsp;',
result: '&lt;',
warn: '⚠'
};
@observer
export default class Console extends Component {
consoleStore = ConsoleStore.get();
settingsStore = SettingsStore.get();
render () {
return (
<div ref={ this.setRef }>
{ this.renderResults() }
</div>
);
}
renderResults () {
const { logs } = this.consoleStore;
return logs.map((data, index) => {
const { type, timestamp } = data;
const values = this.consoleStore.logValues[index];
const classes = [ styles.result, styles[type] ];
return (
<div
className={ classes.join(' ') }
key={ index }
>
<span
className={ styles.type }
dangerouslySetInnerHTML={ { __html: ICONS[type] || '' } }
/>
{ this.renderTimestamp(timestamp) }
<span className={ styles.text }>
{
values.map((value, valueIndex) => (
<span
className={ styles.token }
key={ valueIndex }
>
{ this.toString(value) }
</span>
))
}
</span>
</div>
);
});
}
renderTimestamp (timestamp) {
const { displayTimestamps } = this.settingsStore;
if (!displayTimestamps) {
return null;
}
return (
<span className={ styles.time }>
{ new Date(timestamp).toISOString().slice(11, 23) }
</span>
);
}
setRef = (node) => {
const element = ReactDOM.findDOMNode(node);
this.consoleStore.setNode(element);
};
toString (value) {
if (typeof value === 'string') {
return value;
}
if (value instanceof Error) {
return value.toString();
}
return (
<ObjectInspector data={ value } />
);
}
}

View File

@ -0,0 +1,126 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
import AutocompleteStore from '../Autocomplete/autocomplete.store';
import { evaluate } from '../utils';
let instance;
export default class ConsoleStore {
@observable logs = [];
autocompleteStore = AutocompleteStore.get();
logValues = [];
node = null;
constructor () {
this.attachConsole();
}
static get () {
if (!instance) {
instance = new ConsoleStore();
}
return instance;
}
attachConsole () {
['debug', 'error', 'info', 'log', 'warn'].forEach((level) => {
const old = window.console[level].bind(window.console);
window.console[level] = (...args) => {
old(...args);
this.log({ type: level, values: args });
};
});
}
@action
clear () {
this.logs = [];
this.logValues = [];
}
evaluate (input) {
this.log({ type: 'input', value: input });
setTimeout(() => {
const { result, error } = evaluate(input);
let value = error || result;
const type = error
? 'error'
: 'result';
if (typeof value === 'string') {
value = `"${value}"`;
}
if (value && typeof value === 'object' && typeof value.then === 'function') {
return value
.then((result) => {
this.log({ type: 'result', value: result });
})
.catch((error) => {
this.log({ type: 'error', value: error });
});
}
this.log({ type, value });
});
}
@action
log ({ type, value, values }) {
this.logs.push({
type,
timestamp: Date.now()
});
if (values) {
this.logValues.push(values);
} else {
this.logValues.push([ value ]);
}
this.autocompleteStore.setPosition();
this.scroll();
}
setNode (node) {
this.node = node;
this.scroll();
}
scroll () {
if (!this.node) {
return;
}
setTimeout(() => {
if (this.node.children.length === 0) {
return;
}
// Scroll to the last child
this.node
.children[this.node.children.length - 1]
.scrollIntoView(false);
}, 50);
}
}

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './console';

View File

@ -0,0 +1,51 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.container {
background-color: #f3f3f3;
border-bottom: 1px solid #ccc;
font-size: 12px;
padding: 0 0.5em;
}
.tabs {
display: flex;
}
.tab {
align-items: center;
box-sizing: border-box;
border: 1px solid transparent;
color: #333;
cursor: default;
display: flex;
height: 24px;
line-height: 15px;
margin-top: 2px;
padding: 2px 6px 2px 4px;
&:hover,
&.active:hover {
background-color: #e5e5e5;
}
&.active {
background-color: white;
border: 1px solid #ccc;
border-bottom: none;
}
}

View File

@ -0,0 +1,65 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import ApplicationStore from '../Application/application.store';
import styles from './header.css';
@observer
export default class Header extends Component {
application = ApplicationStore.get();
render () {
return (
<div className={ styles.container }>
<div className={ styles.tabs }>
{ this.renderTabs() }
</div>
</div>
);
}
renderTabs () {
const { view } = this.application;
return this.application.views.map((tab) => {
const { label, id } = tab;
const classes = [ styles.tab ];
const onClick = () => this.handleClickTab(id);
if (id === view) {
classes.push(styles.active);
}
return (
<div
className={ classes.join(' ') }
key={ id }
onClick={ onClick }
>
{ label }
</div>
);
});
}
handleClickTab = (id) => {
this.application.setView(id);
};
}

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './header';

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './input';

View File

@ -0,0 +1,46 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.type {
color: #59f;
font-weight: bold !important;
font-size: 11px;
padding: 0 0.5em 0 0.25em;
}
.inputContainer {
flex: 1;
}
.input {
border: 0;
margin: 0;
padding: 0;
color: black;
height: 100%;
font-size: 11px;
resize: none;
width: 100%;
}
.container {
border-top: 1px solid lightgray;
display: flex;
flex: 1;
padding: 0.25em;
position: relative;
}

View File

@ -0,0 +1,145 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import keycode from 'keycode';
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Autocomplete from '../Autocomplete';
import AutocompleteStore from '../Autocomplete/autocomplete.store';
import ConsoleStore from '../Console/console.store';
import InputStore from './input.store';
import SettingsStore from '../Settings/settings.store';
import styles from './input.css';
@observer
export default class Input extends Component {
autocompleteStore = AutocompleteStore.get();
consoleStore = ConsoleStore.get();
inputStore = InputStore.get();
settingsStore = SettingsStore.get();
render () {
const { input } = this.inputStore;
return (
<div className={ styles.container }>
<Autocomplete />
<span className={ styles.type }>&gt;</span>
<div className={ styles.inputContainer }>
<textarea
autoFocus
className={ styles.input }
onChange={ this.handleChange }
onKeyDown={ this.handleKeyDown }
ref={ this.setRef }
rows={ input.split('\n').length }
type='text'
value={ input }
/>
</div>
</div>
);
}
handleChange = (event) => {
const { value } = event.target;
this.inputStore.updateInput(value);
};
handleKeyDown = (event) => {
const { executeOnEnter } = this.settingsStore;
const { input } = this.inputStore;
const codeName = keycode(event);
const multilines = input.split('\n').length > 1;
// Clear console with CTRL+L
if (codeName === 'l' && event.ctrlKey) {
event.preventDefault();
event.stopPropagation();
return this.consoleStore.clear();
}
if (codeName === 'esc') {
event.preventDefault();
event.stopPropagation();
return this.autocompleteStore.hide();
}
if (codeName === 'enter') {
if (event.shiftKey) {
return;
}
// If not execute on enter: execute on
// enter + CTRL
if (!executeOnEnter && !event.ctrlKey) {
return;
}
event.preventDefault();
event.stopPropagation();
if (this.autocompleteStore.hasSelected) {
return this.autocompleteStore.select(this.inputStore);
}
if (input.length > 0) {
return this.inputStore.execute();
}
}
if (codeName === 'up' && !multilines) {
event.preventDefault();
event.stopPropagation();
if (this.autocompleteStore.show) {
return this.autocompleteStore.focus(-1);
}
return this.inputStore.selectHistory(-1);
}
if (codeName === 'down' && !multilines) {
event.preventDefault();
event.stopPropagation();
if (this.autocompleteStore.show) {
return this.autocompleteStore.focus(1);
}
return this.inputStore.selectHistory(1);
}
if (codeName === 'left' && this.autocompleteStore.show) {
return this.autocompleteStore.hide();
}
if (codeName === 'right' && this.autocompleteStore.show) {
event.preventDefault();
event.stopPropagation();
return this.autocompleteStore.select(this.inputStore);
}
};
setRef = (node) => {
this.inputStore.setInputNode(ReactDOM.findDOMNode(node));
};
}

View File

@ -0,0 +1,124 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
import store from 'store';
import AutocompleteStore from '../Autocomplete/autocomplete.store';
import ConsoleStore from '../Console/console.store';
const LS_HISTORY_KEY = '_console::history';
const MAX_HISTORY_LINES = 5;
let instance;
export default class InputStore {
@observable input = '';
autocompleteStore = AutocompleteStore.get();
consoleStore = ConsoleStore.get();
history = [];
historyOffset = null;
inputNode = null;
lastInput = '';
constructor () {
this.loadHistory();
}
static get () {
if (!instance) {
instance = new InputStore();
}
return instance;
}
setInputNode (node) {
this.inputNode = node;
this.autocompleteStore.setInputNode(node);
}
@action
updateInput (nextValue = '', updateAutocomplete = true) {
this.input = nextValue;
const multilines = nextValue.split('\n').length > 1;
if (updateAutocomplete && !multilines) {
this.autocompleteStore.update(nextValue);
}
}
selectHistory (_offset) {
// No history
if (this.history.length === 0) {
return;
}
if (this.historyOffset === null) {
// Can't go down if no history selected
if (_offset === 1) {
return;
}
this.historyOffset = this.history.length - 1;
this.lastInput = this.input;
return this.updateInput(this.history[this.historyOffset], false);
}
if (_offset === 1 && this.historyOffset === this.history.length - 1) {
this.historyOffset = null;
return this.updateInput(this.lastInput);
}
this.historyOffset = Math.max(0, this.historyOffset + _offset);
const nextInput = this.history[this.historyOffset];
this.updateInput(nextInput, false);
}
execute () {
const { input } = this;
this.pushToHistory(input);
this.consoleStore.evaluate(input);
this.updateInput('');
this.historyOffset = null;
this.autocompleteStore.clearCache();
}
pushToHistory (input) {
// Don't stack twice the same input in
// history
if (this.history[this.history.length - 1] !== input) {
this.history.push(input);
}
this.saveHistory();
}
loadHistory () {
this.history = store.get(LS_HISTORY_KEY) || [];
}
saveHistory () {
if (this.history.length > MAX_HISTORY_LINES) {
this.history = this.history.slice(-1 * MAX_HISTORY_LINES);
}
store.set(LS_HISTORY_KEY, this.history.slice());
}
}

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './settings';

View File

@ -0,0 +1,32 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.container {
display: flex;
flex-direction: row;
font-family: Arial, sans-serif;
font-size: 12px;
padding: 0.5em 1em;
}
.option {
align-items: center;
display: flex;
flex: 0 0 50%;
flex-direction: row;
margin: 0.5em 0;
}

View File

@ -0,0 +1,70 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import SettingsStore from './settings.store';
import styles from './settings.css';
@observer
export default class Settings extends Component {
settingsStore = SettingsStore.get();
render () {
const { displayTimestamps, executeOnEnter } = this.settingsStore;
return (
<div className={ styles.container }>
<div className={ styles.option }>
<input
checked={ executeOnEnter }
id='executeOnEnter'
onChange={ this.handleExecuteOnEnterChange }
type='checkbox'
/>
<label htmlFor='executeOnEnter'>
Execute on <code>Enter</code>
</label>
</div>
<div className={ styles.option }>
<input
checked={ displayTimestamps }
id='displayTimestamps'
onChange={ this.handleDisplayTimestampsChange }
type='checkbox'
/>
<label htmlFor='displayTimestamps'>
Show timestamps
</label>
</div>
</div>
);
}
handleDisplayTimestampsChange = (event) => {
const { checked } = event.target;
this.settingsStore.setDisplayTimestamps(checked);
};
handleExecuteOnEnterChange = (event) => {
const { checked } = event.target;
this.settingsStore.setExecuteOnEnter(checked);
};
}

View File

@ -0,0 +1,71 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
import store from 'store';
const LS_SETTINGS_KEY = '_console::settings';
let instance;
export default class SettingsStore {
@observable displayTimestamps = true;
@observable executeOnEnter = true;
constructor () {
this.load();
}
static get () {
if (!instance) {
instance = new SettingsStore();
}
return instance;
}
load () {
const settings = store.get(LS_SETTINGS_KEY) || {};
const { executeOnEnter, displayTimestamps } = settings;
if (executeOnEnter !== undefined) {
this.setExecuteOnEnter(executeOnEnter);
}
if (displayTimestamps !== undefined) {
this.setDisplayTimestamps(displayTimestamps);
}
}
save () {
const { executeOnEnter, displayTimestamps } = this;
const settings = { executeOnEnter, displayTimestamps };
store.set(LS_SETTINGS_KEY, settings);
}
@action
setDisplayTimestamps (value) {
this.displayTimestamps = value;
this.save();
}
@action
setExecuteOnEnter (value) {
this.executeOnEnter = value;
this.save();
}
}

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './snippets';

View File

@ -0,0 +1,122 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.container {
display: flex;
flex: 1;
flex-direction: row;
}
.panel {
border-right: 1px solid lightgray;
display: flex;
flex-direction: column;
min-width: 200px;
}
.add {
align-items: center;
background-color: #fcfcfc;
border-bottom: 1px solid lightgray;
cursor: default;
display: flex;
padding: 0.5em 1em;
.plus {
font-size: 15px;
font-weight: bold !important;
margin-right: 5px;
}
&:hover {
background-color: #f0f0f0;
}
}
.list {
display: flex;
flex-direction: column;
margin-top: 3px;
}
.code {
display: flex;
flex: 1;
flex-direction: column;
.console {
border-top: 1px solid lightgray;
max-height: 200px;
flex: 0 0 0;
> * {
overflow: auto;
height: 100%;
}
}
> * {
flex: 1;
}
:global(.CodeMirror) {
height: 100%;
}
}
.file {
align-items: center;
cursor: default;
display: flex;
padding: 0.5em 0.5em 0.5em 1em;
&.selected {
background-color: #f0f0f0;
}
&:hover {
background-color: rgb(230, 236, 255);
}
.pristine {
font-size: 20px;
margin-right: 3px;
height: 13px;
}
.remove {
cursor: default;
display: inline-flex;
font-size: 14px;
margin-left: -0.25em;
margin-right: 0.25em;
}
}
.inputContainer {
background-color: white;
border: solid 1px #d8d8d8;
margin-right: 0.5em;
padding: 3px;
width: 100%;
}
.input {
border: none;
font: 11px Arial;
width: 100%;
}

View File

@ -0,0 +1,221 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import keycode from 'keycode';
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import CodeMirror from 'react-codemirror';
import EventListener from 'react-event-listener';
import Console from '../Console';
import SnippetsStore from './snippets.store';
import styles from './snippets.css';
@observer
export default class Snippets extends Component {
snippetsStore = SnippetsStore.get();
render () {
const { code } = this.snippetsStore;
return (
<div className={ styles.container }>
<EventListener
onKeyDown={ this.handleKeyDown }
target='window'
/>
<div className={ styles.panel }>
<div
className={ styles.add }
onClick={ this.handleAddFile }
>
<span className={ styles.plus }>+</span>
<span>New Snippet</span>
</div>
<div className={ styles.list }>
{ this.renderFiles() }
</div>
</div>
<div className={ styles.code }>
<CodeMirror
ref={ this.setRef }
onChange={ this.handleChange }
options={ {
autofocus: true,
extraKeys: {
'Ctrl-Space': 'autocomplete'
},
keyMap: 'sublime',
highlightSelectionMatches: {
delay: 0,
showToken: false
},
lineNumbers: true,
mode: 'javascript'
} }
value={ code }
/>
<div className={ styles.console }>
<Console />
</div>
</div>
</div>
);
}
renderFiles () {
const { files } = this.snippetsStore;
return files
.values()
.sort((fa, fb) => fa.name.localeCompare(fb.name))
.map((file) => this.renderFile(file));
}
renderFile (file) {
const { nextName, renaming, selected } = this.snippetsStore;
const { id, name } = file;
const classes = [ styles.file ];
if (renaming === id) {
return (
<div
className={ classes.join(' ') }
key={ id }
>
<EventListener
onClick={ this.handleSaveName }
target='window'
/>
<div className={ styles.inputContainer }>
<input
autoFocus
className={ styles.input }
onClick={ this.stopPropagation }
onChange={ this.handleNameChange }
onKeyDown={ this.handleRenameKeyDown }
type='text'
value={ nextName }
/>
</div>
</div>
);
}
const onClick = () => this.handleSelectFile(id);
const onDoubleClick = () => this.handleRenameFile(id);
const onRemove = (event) => this.handleRemove(id, event);
if (selected === id) {
classes.push(styles.selected);
}
return (
<div
className={ classes.join(' ') }
key={ id }
onClick={ onClick }
onDoubleClick={ onDoubleClick }
>
<span
className={ styles.remove }
onClick={ onRemove }
title={ `Remove ${name}` }
>
</span>
<span className={ styles.pristine }>
{
file.isPristine
? null
: '*'
}
</span>
<span>
{ name }
</span>
</div>
);
}
handleAddFile = () => {
this.snippetsStore.create();
};
handleSaveName = (event) => {
this.snippetsStore.saveName();
return event;
};
handleChange = (value) => {
this.snippetsStore.edit(value);
};
handleKeyDown = (event) => {
const codeName = keycode(event);
if (codeName === 's' && event.ctrlKey) {
event.preventDefault();
event.stopPropagation();
return this.snippetsStore.save();
}
};
handleNameChange = (event) => {
const { value } = event.target;
this.snippetsStore.updateName(value);
};
handleRemove = (id, event) => {
this.snippetsStore.remove(id);
event.stopPropagation();
};
handleRenameFile = (id) => {
this.snippetsStore.startRename(id);
};
handleRenameKeyDown = (event) => {
const codeName = keycode(event);
if (codeName === 'enter') {
return this.snippetsStore.saveName();
}
if (codeName === 'esc') {
return this.snippetsStore.cancelRename();
}
};
handleSelectFile = (id) => {
this.snippetsStore.select(id);
};
setRef = (node) => {
const codeMirror = node
? node.getCodeMirror()
: null;
this.snippetsStore.setCodeMirror(codeMirror);
};
stopPropagation = (event) => {
event.stopPropagation();
};
}

View File

@ -0,0 +1,249 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import keycode from 'keycode';
import { action, computed, map, observable, transaction } from 'mobx';
import store from 'store';
import { evaluate } from '../utils';
const LS_SNIPPETS_KEY = '_console::snippets';
let instance;
export default class SnippetsStore {
@observable files = map();
@observable nextName = null;
@observable renaming = null;
@observable selected = null;
codeMirror = null;
constructor () {
this.load();
}
static get () {
if (!instance) {
instance = new SnippetsStore();
}
return instance;
}
@computed
get code () {
if (!this.selected) {
return '';
}
return this.files.get(this.selected).content;
}
@action
cancelRename () {
if (!this.renaming || !this.nextName) {
return;
}
this.renaming = null;
this.nextName = null;
}
clearCodeHistory () {
if (this.codeMirror) {
this.codeMirror.doc.clearHistory();
}
}
@action
create () {
const id = this.getNewId();
const file = {
content: '',
isPristine: false,
name: `Snippet #${id}`,
id
};
transaction(() => {
this.files.set(id, file);
this.select(id);
});
}
edit (value) {
if (!this.selected) {
this.create();
}
const file = this.files.get(this.selected);
file.content = value;
this.updateFile(file);
}
evaluate () {
const code = this.code;
if (!code) {
return;
}
const { result, error } = evaluate(code);
if (error) {
console.error(error);
} else {
console.log(result);
}
}
getFromStorage () {
return store.get(LS_SNIPPETS_KEY) || [];
}
getNewId () {
if (this.files.size === 0) {
return 1;
}
const ids = this.files.values().map((file) => file.id);
return Math.max(...ids) + 1;
}
load () {
const files = this.getFromStorage();
transaction(() => {
files.forEach((file) => {
this.files.set(file.id, file);
});
});
}
@action
remove (id) {
transaction(() => {
if (id === this.selected) {
this.selected = null;
}
this.files.delete(id);
const files = this.getFromStorage()
.filter((f) => f.id !== id);
return store.set(LS_SNIPPETS_KEY, files);
});
}
save (_file) {
let file;
if (!_file) {
if (!this.selected) {
return false;
}
file = this.files.get(this.selected);
} else {
file = _file;
}
file.savedContent = file.content;
this.updateFile(file);
this.saveToStorage(file);
}
saveName () {
if (!this.renaming || !this.nextName) {
return;
}
const file = this.files.get(this.renaming);
file.name = this.nextName;
this.save(file);
this.cancelRename();
}
saveToStorage (file) {
const files = this.getFromStorage();
const index = files.findIndex((f) => file.id === f.id);
if (index === -1) {
files.push(file);
} else {
files[index] = file;
}
return store.set(LS_SNIPPETS_KEY, files);
}
@action
select (id) {
this.selected = id;
// Wait for the file content to be loaded
setTimeout(() => {
this.clearCodeHistory();
}, 50);
}
setCodeMirror (codeMirror) {
this.codeMirror = codeMirror;
if (!codeMirror) {
return;
}
this.codeMirror
.on('keydown', (_, event) => {
const codeName = keycode(event);
if (codeName === 'enter' && event.ctrlKey) {
event.preventDefault();
event.stopPropagation();
return this.evaluate();
}
});
}
@action
startRename (id) {
const file = this.files.get(id);
transaction(() => {
this.renaming = id;
this.nextName = file.name;
});
}
@action
updateFile (file) {
file.isPristine = (file.content === file.savedContent);
this.files.set(file.id, file);
}
@action
updateName (value) {
this.nextName = value;
}
}

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './watches';

View File

@ -0,0 +1,107 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.container {
background: #eee;
border-top: 1px solid lightgray;
display: flex;
flex-direction: row;
padding: 0.25em;
font-size: 8pt;
color: #888;
}
.watch {
align-items: flex-end;
border-left: 1px solid #ccc;
display: flex;
padding: 0 1em;
&.error .result {
color: red;
}
.result {
color: #00f;
font-weight: bold;
margin-left: 0.5em;
}
}
.add {
color: green;
cursor: default;
font-size: 14px;
font-weight: bold !important;
padding: 0 1em;
&:hover {
background-color: #fcfcfc;
}
&.selected {
background-color: rgb(230, 236, 255);
}
}
.addContainer {
position: relative;
}
.addForm {
bottom: 100%;
display: flex;
padding: 0.5em 0;
position: absolute;
width: 100vw;
.inputContainer {
border: solid 1px #d8d8d8;
margin-right: 0.5em;
padding: 3px;
}
.input {
border: none;
font: 12px Arial;
&.big {
width: 300px;
}
}
.button {
color: black;
font: 12px Arial;
padding: 0 1em;
}
}
.remove {
cursor: default;
display: inline-flex;
font-size: 14px;
margin-right: 0.75em;
* {
line-height: 13px;
}
&:hover {
background-color: #fcfcfc;
}
}

View File

@ -0,0 +1,186 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { observer } from 'mobx-react';
import React, { Component } from 'react';
import WatchesStore from './watches.store';
import styles from './watches.css';
@observer
export default class Watches extends Component {
watchesStore = WatchesStore.get();
render () {
return (
<div className={ styles.container }>
{ this.renderAddWatch() }
{ this.renderWatches() }
</div>
);
}
renderAddForm () {
const { showAdd } = this.watchesStore;
if (!showAdd) {
return null;
}
return (
<div className={ styles.addForm }>
<div className={ styles.inputContainer }>
<input
className={ styles.input }
onChange={ this.handleAddNameChange }
placeholder='Name'
type='text'
/>
</div>
<div className={ styles.inputContainer }>
<input
className={ [ styles.input, styles.big ].join(' ') }
onChange={ this.handleAddFunctionChange }
placeholder='Function'
type='text'
/>
</div>
<div className={ styles.inputContainer }>
<input
className={ styles.input }
onChange={ this.handleAddContextChange }
placeholder='Context'
type='text'
/>
</div>
<button
className={ styles.button }
onClick={ this.handleAddWatch }
>
Add
</button>
</div>
);
}
renderAddWatch () {
const { showAdd } = this.watchesStore;
const classes = [ styles.add ];
if (showAdd) {
classes.push(styles.selected);
}
return (
<div className={ styles.addContainer }>
{ this.renderAddForm() }
<span
className={ classes.join(' ') }
onClick={ this.handleToggleAdd }
>
+
</span>
</div>
);
}
renderWatches () {
const { names } = this.watchesStore;
return names.map((name) => {
const { result, error } = this.watchesStore.get(name);
const classes = [ styles.watch ];
const resultStr = error
? error.toString()
: this.toString(result);
const onClick = () => this.handleRemoveWatch(name);
if (error) {
classes.push(styles.error);
}
return (
<div
className={ classes.join(' ') }
key={ name }
>
<span
className={ styles.remove }
onClick={ onClick }
title={ `Remove "${name}" watch` }
>
<span></span>
</span>
<span>{ name }</span>
<span className={ styles.result }>{ resultStr.toString() }</span>
</div>
);
});
}
handleAddFunctionChange = (event) => {
const { value } = event.target;
this.watchesStore.updateAddFunction(value);
};
handleAddContextChange = (event) => {
const { value } = event.target;
this.watchesStore.updateAddContext(value);
};
handleAddNameChange = (event) => {
const { value } = event.target;
this.watchesStore.updateAddName(value);
};
handleAddWatch = () => {
this.watchesStore.addWatch();
};
handleRemoveWatch = (name) => {
this.watchesStore.remove(name);
};
handleToggleAdd = () => {
this.watchesStore.toggleAdd();
};
toString (result) {
if (result === undefined) {
return 'undefined';
}
if (result === null) {
return 'null';
}
if (typeof result.toFormat === 'function') {
return result.toFormat();
}
if (typeof result.toString === 'function') {
return result.toString();
}
return result;
}
}

View File

@ -0,0 +1,148 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, map, observable } from 'mobx';
import { api } from '../parity';
import { evaluate } from '../utils';
let instance;
export default class WatchesStore {
@observable showAdd = false;
@observable watches = map();
watchesFunctions = {};
constructor () {
api.subscribe('eth_blockNumber', () => {
this.refreshWatches();
});
}
static get () {
if (!instance) {
instance = new WatchesStore();
window.watch = instance.add.bind(instance);
window.unwatch = instance.remove.bind(instance);
}
return instance;
}
@action
add (name, func, context) {
if (!func || (typeof func !== 'function' && typeof func.then !== 'function')) {
return console.error(Error(`cannot watch ${name} ; not a Function/Promise given`));
}
this.watchesFunctions[name] = { func, context };
this.watches.set(name, {});
this.refreshWatches();
}
addWatch () {
this.toggleAdd();
const { addContext, addFunction, addName } = this;
const evaluatedFunction = evaluate(addFunction);
const evaluatedContext = addContext
? evaluate(addContext)
: {};
this.add(addName, evaluatedFunction.result, evaluatedContext.result);
}
get (name) {
return this.watches.get(name);
}
get names () {
return this.watches.keys();
}
refreshWatches () {
const names = this.watches.keys();
const promises = names
.map((name) => {
const { context, func } = this.watchesFunctions[name];
let result;
try {
if (typeof func === 'function') {
result = func.apply(context || this);
} else {
result = func;
}
return Promise.resolve(result);
} catch (error) {
return Promise.reject(error);
}
})
.map((promise, index) => {
const name = names[index];
return promise
.then((result) => {
this.updateWatch(name, result);
})
.catch((error) => {
this.updateWatch(name, error, true);
});
});
return Promise.all(promises);
}
@action
remove (name) {
this.watches.delete(name);
delete this.watchesFunctions[name];
}
@action
toggleAdd () {
this.showAdd = !this.showAdd;
}
updateAddContext (value) {
this.addContext = value;
}
updateAddFunction (value) {
this.addFunction = value;
}
updateAddName (value) {
this.addName = value;
}
@action
updateWatch (name, result, isError = false) {
const next = {};
if (isError) {
next.error = result;
} else {
next.result = result;
}
this.watches.set(name, { ...next });
}
}

View File

@ -0,0 +1,36 @@
/* Copyright 2015-2017 Parity Technologies (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
:global {
.cm-matchhighlight {
background-color: rgba(255, 255, 0, 0.4);
}
.CodeMirror-hints {
font-family: dejavu sans mono, monospace;
font-size: 11px;
border-radius: 0;
border: none;
padding: 0;
}
.CodeMirror-hint {
border-radius: 0;
cursor: default;
padding: 0.25em 0.25em 0.25em 0.35em;
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,36 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable no-eval */
export function evaluate (input) {
try {
const result = eval.apply(window, [ `${input}` ]);
return { result };
} catch (err) {
try {
const result = eval.apply(window, [ `(function () {
var x = ${input};
return x;
})()` ]);
return { result };
} catch (error) {
return { error };
}
}
}

View File

View File

@ -1,221 +0,0 @@
#full-screen {
}
textarea:focus, input:focus{
outline: none;
}
* { padding: 0; margin: 0; }
html, body, #full-screen {
height: 100%;
}
body {
margin: 0;
}
#full-screen {
display: flex;
flex-direction: column;
justify-content: flex-end;
overflow: hidden;
}
#history-wrap {
overflow: auto;
}
#history {
flex: 1 1 auto;
display: flex;
min-height: min-content;
flex-direction: column;
justify-content: flex-end;
}
.entry, #input {
margin: 0;
padding: 0.25em;
display: flex;
flex-direction: row;
align-items: flex-start;
width: 100%;
}
.type {
color: #ccc;
font-weight: bold;
font-family: "Lucida Console", Monaco, monospace;
font-size: 11pt;
padding: 0 0.5em 0 0.25em;
}
.command .type {
color: #aaa;
}
.result .type {
color: #ccc;
}
#input .type {
color: #59f;
}
.addwatch {
background-color: #cfc;
border-top: 1px solid #6e6;
}
.addwatch .type {
color: #040;
}
.addwatch .text {
color: #080;
}
.log.logLevel .type {
color: blue;
}
.log.debugLevel .text {
color: blue;
}
.log.infoLevel .type {
color: #00c;
}
.log.warnLevel {
background-color: #fff8dd;
border-top: 1px solid #fe8;
border-bottom: 1px solid #fe8;
}
.log.warnLevel .text {
color: #440;
}
.log.warnLevel .type {
color: #880;
}
.log.errorLevel, .error {
background-color: #fee;
border-top: 1px solid #fbb;
border-bottom: 1px solid #fbb;
}
.log.errorLevel .text, .error .text {
color: #c00;
}
.log.errorLevel .type, .error .type {
color: #800;
}
span.text {
color: black;
}
.text {
border: 0;
margin: 0;
padding: 0;
color: black;
font-weight: 1000;
font-family: "Lucida Console", Monaco, monospace;
font-size: 11pt;
flex-grow: 1;
}
div.error {
background-color: #fee;
border-top: 1px solid #fbb;
color: red;
}
div.command {
border-top: 1px solid #eee;
}
div#input {
border-top: 1px solid #eee;
}
#status {
background: #eee;
padding: 0.25em;
font-family: "Lucida Console", Monaco, monospace;
font-size: 8pt;
color: #888;
flex: 0 0 auto;
}
#input {
flex: 0 0 auto;
}
#status {
display: flex;
}
.watch {
padding-left: 1em;
padding-right: 1em;
}
.watch:not(:first-child) {
border-left: 1px solid #ccc;
}
.expr {
color: #888;
margin-right: 0.5em;
}
.res {
font-weight: bold;
color: black;
}
.stringType {
color: #c00;
}
.numberType {
color: #00f;
}
.eObject {
color: #00f;
}
.objectType {
font-style: italic;
}
.fieldType {
color: #808;
}
.undefinedType {
color: #888;
}
.functionType {
color: #666;
font-style: italic;
}
#autocomplete-anchor {
position: relative;
}
#autocomplete {
position: absolute;
left: 1.5em;
bottom: 0;
background: #f8f8f8;
box-shadow: 0 0.125em 0.25em rgba(0, 0, 0, 0.5);
max-height: 20em;
overflow: scroll;
}
#autocomplete div {
font-size: small;
font-family: "Lucida Console", Monaco, monospace;
padding: 0.1em 1em 0.1em 0;
border-top: 1px solid #eee;
}
.ac-already {
font-weight: bold;
}
.ac-new {
color: #666;
}
.ac-selected {
background-color: #ccddff;
}

View File

@ -1,19 +0,0 @@
<!doctype html>
<html lang="us">
<head>
<meta charset="utf-8">
<title>JS Console dapp</title>
<link href="console.css" rel='stylesheet' type='text/css'>
<script src="/web3.js"></script>
</head>
<body>
<div id="full-screen">
<div id="eval"></div>
<div id="history-wrap"><div id="history"></div></div>
<div id="autocomplete-anchor"><div id="autocomplete"></div></div>
<div id="input"><span class="type">&gt;</span><input type="text" class="text" id="command" list="input-datalist"></div>
<div id="status"></div>
</div>
<script src="console.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -22,7 +22,7 @@
color: #333;
font-size: 16px;
font-family: 'Roboto', sans-serif;
font-weight: 300 !important;
font-weight: 300;
margin: 0;
padding: 0;
vertical-align: top;
@ -39,7 +39,7 @@
}
:root * {
font-weight: 300 !important;
font-weight: 300;
}
:root :global(#container) > div {

View File

@ -7,6 +7,17 @@
"version": "1.0.0",
"visible": false
},
{
"id": "0xa635a9326814bded464190eddf0bdb90ce92d40ea2359cf553ea80e3c5a4076c",
"url": "console",
"name": "Parity/Web3 console",
"description": "A Javascript development console complete with web3 and parity objects",
"version": "0.3",
"author": "Gav Wood <gavin@parity.io>",
"position": "top-right",
"visible": true,
"secure": true
},
{
"id": "0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f",
"url": "tokendeploy",
@ -84,17 +95,5 @@
"visible": true,
"skipBuild": true,
"skipHistory": true
},
{
"id": "0xa635a9326814bded464190eddf0bdb90ce92d40ea2359cf553ea80e3c5a4076c",
"url": "console",
"name": "Parity/Web3 console",
"description": "A Javascript development console complete with web3 and parity objects",
"version": "0.3",
"author": "Gav Wood <gavin@parity.io>",
"position": "top-right",
"visible": true,
"secure": true,
"skipBuild": true
}
]

View File

@ -1053,7 +1053,7 @@ babel-register@6.23.0, babel-register@^6.23.0:
mkdirp "^0.5.1"
source-map-support "^0.4.2"
babel-runtime@6.23.0, babel-runtime@^6.0.0, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0:
babel-runtime@6.23.0, babel-runtime@^6.0.0, babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.20.0, babel-runtime@^6.22.0, babel-runtime@^6.9.2:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b"
dependencies:
@ -1551,7 +1551,7 @@ clap@^1.0.9:
dependencies:
chalk "^1.1.3"
classnames@2.2.5, classnames@^2.2.0:
classnames@2.2.5, classnames@^2.2.0, classnames@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
@ -1637,6 +1637,10 @@ code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
codemirror@^5.18.2:
version "5.25.2"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.25.2.tgz#8c77677ca9c9248d757d3a07ed1e89a8404850b7"
collapse-white-space@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.2.tgz#9c463fb9c6d190d2dcae21a356a01bcae9eeef6d"
@ -2118,6 +2122,13 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
date-difference@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/date-difference/-/date-difference-1.0.0.tgz#d3e6e883c0cad2dc9dbfcbf12e06297b97298974"
dependencies:
get-stdin "^3.0.2"
meow "^3.1.0"
date-now@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-1.0.1.tgz#bb7d086438debe4182a485fb3df3fbfb99d6153c"
@ -3332,6 +3343,10 @@ get-proxy@^1.0.1:
dependencies:
rc "^1.1.2"
get-stdin@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-3.0.2.tgz#c1ced24b9039b38ded85bdf161e57713b6dd4abe"
get-stdin@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
@ -4086,6 +4101,10 @@ is-date-object@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
is-dom@^1.0.5:
version "1.0.9"
resolved "https://registry.yarnpkg.com/is-dom/-/is-dom-1.0.9.tgz#483832d52972073de12b9fe3f60320870da8370d"
is-dotfile@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.2.tgz#2c132383f39199f8edc268ca01b9b007d205cc4d"
@ -4563,9 +4582,9 @@ keycode@^2.1.1:
version "2.1.8"
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.8.tgz#94d2b7098215eff0e8f9a8931d5a59076c4532fb"
keythereum@0.4.3:
version "0.4.3"
resolved "https://registry.yarnpkg.com/keythereum/-/keythereum-0.4.3.tgz#514acfec3cc4d04b33a21f5d081600719457d2c0"
keythereum@0.4.6:
version "0.4.6"
resolved "https://registry.yarnpkg.com/keythereum/-/keythereum-0.4.6.tgz#0fdec7cf938ae39e5e3061b2f6e16677b766c5da"
dependencies:
elliptic "6.4.0"
ethereumjs-util "5.1.1"
@ -4775,6 +4794,10 @@ lodash.create@3.1.1:
lodash._basecreate "^3.0.0"
lodash._isiterateecall "^3.0.0"
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
lodash.defaults@^4.0.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
@ -5237,11 +5260,11 @@ multipipe@^0.1.2:
dependencies:
duplexer2 "0.0.2"
mute-stream@0.0.5, mute-stream@~0.0.4:
mute-stream@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
mute-stream@0.0.7:
mute-stream@0.0.7, mute-stream@~0.0.4:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
@ -6524,6 +6547,14 @@ react-addons-test-utils@15.4.2:
fbjs "^0.8.4"
object-assign "^4.1.0"
react-codemirror@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/react-codemirror/-/react-codemirror-0.3.0.tgz#cd6bd6ef458ec1e035cfd8b3fe7b30c8c7883c6c"
dependencies:
classnames "^2.2.5"
codemirror "^5.18.2"
lodash.debounce "^4.0.8"
react-container-dimensions@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/react-container-dimensions/-/react-container-dimensions-1.2.0.tgz#bfb5e70e10aa82d2ecba49147d14bb4c22cafdaa"
@ -6596,6 +6627,13 @@ react-hot-loader@3.0.0-beta.6:
redbox-react "^1.2.5"
source-map "^0.4.4"
react-inspector@paritytech/react-inspector:
version "1.1.2"
resolved "https://codeload.github.com/paritytech/react-inspector/tar.gz/73b5214261a5131821eb9088f58d7e5f31210c23"
dependencies:
babel-runtime "^6.9.2"
is-dom "^1.0.5"
react-intl-aggregate-webpack-plugin@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/react-intl-aggregate-webpack-plugin/-/react-intl-aggregate-webpack-plugin-0.0.1.tgz#c29958860d7bcdfa6f460dec79f55ae0220488c6"
@ -6958,7 +6996,7 @@ request-capture-har@^1.1.4:
version "1.2.2"
resolved "https://registry.yarnpkg.com/request-capture-har/-/request-capture-har-1.2.2.tgz#cd692cfb2cc744fd84a3358aac6ee51528cf720d"
request@2, request@^2.75.0, request@^2.81.0:
request@2, request@^2.75.0, request@^2.79.0, request@^2.81.0:
version "2.81.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
dependencies:
@ -6985,7 +7023,7 @@ request@2, request@^2.75.0, request@^2.81.0:
tunnel-agent "^0.6.0"
uuid "^3.0.0"
request@2.79.0, request@^2.79.0:
request@2.79.0:
version "2.79.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de"
dependencies: