diff --git a/js/package.json b/js/package.json
index 5c203a091..bcfe4f5d7 100644
--- a/js/package.json
+++ b/js/package.json
@@ -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",
diff --git a/js/src/dapps/console.js b/js/src/dapps/console.js
new file mode 100644
index 000000000..44b6dcb9c
--- /dev/null
+++ b/js/src/dapps/console.js
@@ -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 .
+
+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(
+
+
+ ,
+ document.querySelector('#container')
+);
+
+if (module.hot) {
+ module.hot.accept('./console/Application/index.js', () => {
+ require('./console/Application/index.js');
+
+ ReactDOM.render(
+
+
+ ,
+ document.querySelector('#container')
+ );
+ });
+}
diff --git a/js/src/dapps/console/Application/application.css b/js/src/dapps/console/Application/application.css
new file mode 100644
index 000000000..eea2c030d
--- /dev/null
+++ b/js/src/dapps/console/Application/application.css
@@ -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 .
+*/
+
+.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;
+}
diff --git a/js/src/dapps/console/Application/application.js b/js/src/dapps/console/Application/application.js
new file mode 100644
index 000000000..5a591e710
--- /dev/null
+++ b/js/src/dapps/console/Application/application.js
@@ -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 .
+
+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 (
+
+ );
+ }
+
+ return null;
+ }
+}
diff --git a/js/src/dapps/console/Application/application.store.js b/js/src/dapps/console/Application/application.store.js
new file mode 100644
index 000000000..c10be46c6
--- /dev/null
+++ b/js/src/dapps/console/Application/application.store.js
@@ -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 .
+
+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;
+ }
+}
diff --git a/js/src/dapps/console/Application/index.js b/js/src/dapps/console/Application/index.js
new file mode 100644
index 000000000..3d8d1ca3b
--- /dev/null
+++ b/js/src/dapps/console/Application/index.js
@@ -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 .
+
+export default from './application';
diff --git a/js/src/dapps/console/Autocomplete/autocomplete.css b/js/src/dapps/console/Autocomplete/autocomplete.css
new file mode 100644
index 000000000..8d4585e7a
--- /dev/null
+++ b/js/src/dapps/console/Autocomplete/autocomplete.css
@@ -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 .
+*/
+
+.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;
+ }
+}
diff --git a/js/src/dapps/console/Autocomplete/autocomplete.js b/js/src/dapps/console/Autocomplete/autocomplete.js
new file mode 100644
index 000000000..e2938f23d
--- /dev/null
+++ b/js/src/dapps/console/Autocomplete/autocomplete.js
@@ -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 .
+
+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 (
+
+ );
+ });
+ }
+
+ handleClick = (index) => {
+ this.autocompleteStore.select(index);
+ };
+
+ setRef = (index, node) => {
+ const element = ReactDOM.findDOMNode(node);
+
+ this.autocompleteStore.setElement(index, element);
+ };
+}
diff --git a/js/src/dapps/console/Autocomplete/autocomplete.store.js b/js/src/dapps/console/Autocomplete/autocomplete.store.js
new file mode 100644
index 000000000..82ff2f24d
--- /dev/null
+++ b/js/src/dapps/console/Autocomplete/autocomplete.store.js
@@ -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 .
+
+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);
+}
diff --git a/js/src/dapps/console/Autocomplete/index.js b/js/src/dapps/console/Autocomplete/index.js
new file mode 100644
index 000000000..5761be0e3
--- /dev/null
+++ b/js/src/dapps/console/Autocomplete/index.js
@@ -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 .
+
+export default from './autocomplete';
diff --git a/js/src/dapps/console/Console/console.css b/js/src/dapps/console/Console/console.css
new file mode 100644
index 000000000..a0b3db4ff
--- /dev/null
+++ b/js/src/dapps/console/Console/console.css
@@ -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 .
+*/
+
+.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;
+}
diff --git a/js/src/dapps/console/Console/console.js b/js/src/dapps/console/Console/console.js
new file mode 100644
index 000000000..75f9713a6
--- /dev/null
+++ b/js/src/dapps/console/Console/console.js
@@ -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 .
+
+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: ' ',
+ error: '✖',
+ info: 'ℹ',
+ input: '>',
+ log: ' ',
+ result: '<',
+ warn: '⚠'
+};
+
+@observer
+export default class Console extends Component {
+ consoleStore = ConsoleStore.get();
+ settingsStore = SettingsStore.get();
+
+ render () {
+ return (
+
+ );
+ });
+ }
+
+ renderTimestamp (timestamp) {
+ const { displayTimestamps } = this.settingsStore;
+
+ if (!displayTimestamps) {
+ return null;
+ }
+
+ return (
+
+ { new Date(timestamp).toISOString().slice(11, 23) }
+
+ );
+ }
+
+ 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 (
+
+ );
+ }
+}
diff --git a/js/src/dapps/console/Console/console.store.js b/js/src/dapps/console/Console/console.store.js
new file mode 100644
index 000000000..dc2fc6db4
--- /dev/null
+++ b/js/src/dapps/console/Console/console.store.js
@@ -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 .
+
+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);
+ }
+}
diff --git a/js/src/dapps/console/Console/index.js b/js/src/dapps/console/Console/index.js
new file mode 100644
index 000000000..2956b330f
--- /dev/null
+++ b/js/src/dapps/console/Console/index.js
@@ -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 .
+
+export default from './console';
diff --git a/js/src/dapps/console/Header/header.css b/js/src/dapps/console/Header/header.css
new file mode 100644
index 000000000..116de6b8c
--- /dev/null
+++ b/js/src/dapps/console/Header/header.css
@@ -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 .
+*/
+
+.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;
+ }
+}
diff --git a/js/src/dapps/console/Header/header.js b/js/src/dapps/console/Header/header.js
new file mode 100644
index 000000000..c422b8256
--- /dev/null
+++ b/js/src/dapps/console/Header/header.js
@@ -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 .
+
+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 (
+
+ );
+ });
+ }
+
+ handleClickTab = (id) => {
+ this.application.setView(id);
+ };
+}
diff --git a/js/src/dapps/console/Header/index.js b/js/src/dapps/console/Header/index.js
new file mode 100644
index 000000000..aef90266f
--- /dev/null
+++ b/js/src/dapps/console/Header/index.js
@@ -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 .
+
+export default from './header';
diff --git a/js/src/dapps/console/Input/index.js b/js/src/dapps/console/Input/index.js
new file mode 100644
index 000000000..29e00f72b
--- /dev/null
+++ b/js/src/dapps/console/Input/index.js
@@ -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 .
+
+export default from './input';
diff --git a/js/src/dapps/console/Input/input.css b/js/src/dapps/console/Input/input.css
new file mode 100644
index 000000000..7b0c2306e
--- /dev/null
+++ b/js/src/dapps/console/Input/input.css
@@ -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 .
+*/
+
+.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;
+}
diff --git a/js/src/dapps/console/Input/input.js b/js/src/dapps/console/Input/input.js
new file mode 100644
index 000000000..3263aff38
--- /dev/null
+++ b/js/src/dapps/console/Input/input.js
@@ -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 .
+
+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 (
+
+
+ >
+
+
+
+
+ );
+ }
+
+ 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));
+ };
+}
diff --git a/js/src/dapps/console/Input/input.store.js b/js/src/dapps/console/Input/input.store.js
new file mode 100644
index 000000000..9d8c2b51a
--- /dev/null
+++ b/js/src/dapps/console/Input/input.store.js
@@ -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 .
+
+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());
+ }
+}
diff --git a/js/src/dapps/console/Settings/index.js b/js/src/dapps/console/Settings/index.js
new file mode 100644
index 000000000..a148ded7a
--- /dev/null
+++ b/js/src/dapps/console/Settings/index.js
@@ -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 .
+
+export default from './settings';
diff --git a/js/src/dapps/console/Settings/settings.css b/js/src/dapps/console/Settings/settings.css
new file mode 100644
index 000000000..8dbe7e743
--- /dev/null
+++ b/js/src/dapps/console/Settings/settings.css
@@ -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 .
+*/
+
+.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;
+}
diff --git a/js/src/dapps/console/Settings/settings.js b/js/src/dapps/console/Settings/settings.js
new file mode 100644
index 000000000..63ae9023f
--- /dev/null
+++ b/js/src/dapps/console/Settings/settings.js
@@ -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 .
+
+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 (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ handleDisplayTimestampsChange = (event) => {
+ const { checked } = event.target;
+
+ this.settingsStore.setDisplayTimestamps(checked);
+ };
+
+ handleExecuteOnEnterChange = (event) => {
+ const { checked } = event.target;
+
+ this.settingsStore.setExecuteOnEnter(checked);
+ };
+}
diff --git a/js/src/dapps/console/Settings/settings.store.js b/js/src/dapps/console/Settings/settings.store.js
new file mode 100644
index 000000000..4c23f8341
--- /dev/null
+++ b/js/src/dapps/console/Settings/settings.store.js
@@ -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 .
+
+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();
+ }
+}
diff --git a/js/src/dapps/console/Snippets/index.js b/js/src/dapps/console/Snippets/index.js
new file mode 100644
index 000000000..48dd44601
--- /dev/null
+++ b/js/src/dapps/console/Snippets/index.js
@@ -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 .
+
+export default from './snippets';
diff --git a/js/src/dapps/console/Snippets/snippets.css b/js/src/dapps/console/Snippets/snippets.css
new file mode 100644
index 000000000..a5004de1c
--- /dev/null
+++ b/js/src/dapps/console/Snippets/snippets.css
@@ -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 .
+*/
+
+.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%;
+}
diff --git a/js/src/dapps/console/Snippets/snippets.js b/js/src/dapps/console/Snippets/snippets.js
new file mode 100644
index 000000000..eeb9459cf
--- /dev/null
+++ b/js/src/dapps/console/Snippets/snippets.js
@@ -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 .
+
+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 (
+