From 3af6423a5576e5b52df37cd6cebc4afbf7cef432 Mon Sep 17 00:00:00 2001 From: Benjamin Kagia Date: Thu, 14 Apr 2016 09:42:16 +0300 Subject: [PATCH 1/3] add code folding in logger #108 A new UI element 'foldable' has been added. It allows code folding for the console. It mirrors the behavior found in most browser consoles, which is to start collapsed and expand upon selection. --- dist/client/ui/action_logger.js | 30 ++------ dist/client/ui/foldable.js | 129 ++++++++++++++++++++++++++++++++ src/client/ui/action_logger.js | 21 +----- src/client/ui/foldable.js | 79 +++++++++++++++++++ 4 files changed, 216 insertions(+), 43 deletions(-) create mode 100644 dist/client/ui/foldable.js create mode 100644 src/client/ui/foldable.js diff --git a/dist/client/ui/action_logger.js b/dist/client/ui/action_logger.js index 45040063d42f..8010ad928912 100644 --- a/dist/client/ui/action_logger.js +++ b/dist/client/ui/action_logger.js @@ -28,6 +28,10 @@ var _react = require('react'); var _react2 = _interopRequireDefault(_react); +var _foldable = require('./foldable'); + +var _foldable2 = _interopRequireDefault(_foldable); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var preStyle = { @@ -63,11 +67,6 @@ var btnStyle = { marginLeft: 5 }; -var latestActionLogStyle = { - backgroundColor: '#FFFCE0', - transition: 'all .2s ease-in' -}; - var ActionLogger = function (_Component) { (0, _inherits3.default)(ActionLogger, _Component); @@ -77,28 +76,11 @@ var ActionLogger = function (_Component) { } (0, _createClass3.default)(ActionLogger, [{ - key: 'componentDidUpdate', - value: function componentDidUpdate() { - var _this2 = this; - - if (this.refs.actionLogger && window.setTimeout) { - this.refs.actionLogger.style.backgroundColor = latestActionLogStyle.backgroundColor; - setTimeout(function () { - _this2.refs.actionLogger.style.backgroundColor = 'white'; - }, 500); - } - } - }, { key: 'getActionData', value: function getActionData() { return this.props.actionLogs.map(function (action, i) { - // assuming that the first object in the array is the latest addition. - return i === 0 ? _react2.default.createElement( - 'div', - { style: latestActionLogStyle, ref: 'actionLogger', key: i }, - action - ) : _react2.default.createElement( - 'div', + return _react2.default.createElement( + _foldable2.default, { key: i }, action ); diff --git a/dist/client/ui/foldable.js b/dist/client/ui/foldable.js new file mode 100644 index 000000000000..f07389022af3 --- /dev/null +++ b/dist/client/ui/foldable.js @@ -0,0 +1,129 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _stringify = require('babel-runtime/core-js/json/stringify'); + +var _stringify2 = _interopRequireDefault(_stringify); + +var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); + +var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); + +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = require('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); + +var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); + +var _inherits2 = require('babel-runtime/helpers/inherits'); + +var _inherits3 = _interopRequireDefault(_inherits2); + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var folderStyle = { + display: 'block', + width: '100%', + marginBottom: '10px', + backgroundColor: 'white', + transition: 'background-color .2s ease-in' +}; + +var folderSidebarStyle = { + display: 'block', + width: '10px', + float: 'left', + height: '100%', + color: '#ccc', + userSelect: 'none', + WebkitUserSelect: 'none', + msUserSelect: 'none', + MozUserSelect: 'none', + cursor: 'pointer' +}; + +var folderContentStyle = { + display: 'inline-block', + clear: 'right', + marginLeft: '5px', + padding: '0', + paddingLeft: '5px', + width: 'auto' +}; + +var Foldable = function (_React$Component) { + (0, _inherits3.default)(Foldable, _React$Component); + + function Foldable(props) { + (0, _classCallCheck3.default)(this, Foldable); + + var _this = (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(Foldable).call(this, props)); + + _this.state = { collapsed: true }; + + _this.onToggleCallback = _this.onToggle.bind(_this); + return _this; + } + + (0, _createClass3.default)(Foldable, [{ + key: 'componentDidMount', + value: function componentDidMount() { + var _this2 = this; + + this.refs.folder.style.backgroundColor = '#FFFCE0'; + setTimeout(function () { + _this2.refs.folder.style.backgroundColor = folderStyle.backgroundColor; + }, 500); + } + }, { + key: 'onToggle', + value: function onToggle() { + this.setState({ collapsed: !this.state.collapsed }); + } + }, { + key: 'render', + value: function render() { + var content = this.props.children; + + if (this.state.collapsed) { + // return the shortest string representation possible + content = (0, _stringify2.default)(JSON.parse(content)); + } + + return _react2.default.createElement( + 'div', + { ref: 'folder', style: folderStyle }, + _react2.default.createElement( + 'div', + { style: folderSidebarStyle, onClick: this.onToggleCallback }, + this.state.collapsed ? '►' : '▼' + ), + _react2.default.createElement( + 'div', + { style: folderContentStyle }, + content + ) + ); + } + }]); + return Foldable; +}(_react2.default.Component); + +Foldable.propTypes = { + children: _react2.default.PropTypes.string +}; + +exports.default = Foldable; \ No newline at end of file diff --git a/src/client/ui/action_logger.js b/src/client/ui/action_logger.js index 62ecb61fdf99..40d3baacb8e8 100644 --- a/src/client/ui/action_logger.js +++ b/src/client/ui/action_logger.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import Foldable from './foldable'; const preStyle = { color: '#666', @@ -36,30 +37,12 @@ const btnStyle = { marginLeft: 5, }; -const latestActionLogStyle = { - backgroundColor: '#FFFCE0', - transition: 'all .2s ease-in', -}; - class ActionLogger extends Component { - componentDidUpdate() { - if (this.refs.actionLogger && window.setTimeout) { - this.refs.actionLogger.style.backgroundColor = latestActionLogStyle.backgroundColor; - setTimeout(() => { - this.refs.actionLogger.style.backgroundColor = 'white'; - }, 500); - } - } getActionData() { return this.props.actionLogs .map((action, i) => { - // assuming that the first object in the array is the latest addition. - return i === 0 ? ( -
{action}
- ) : ( -
{action}
- ); + return { action }; }); } diff --git a/src/client/ui/foldable.js b/src/client/ui/foldable.js new file mode 100644 index 000000000000..85137b1299be --- /dev/null +++ b/src/client/ui/foldable.js @@ -0,0 +1,79 @@ +import React from 'react'; + +const folderStyle = { + display: 'block', + width: '100%', + marginBottom: '10px', + backgroundColor: 'white', + transition: 'background-color .2s ease-in', +}; + +const folderSidebarStyle = { + display: 'block', + width: '10px', + float: 'left', + height: '100%', + color: '#ccc', + userSelect: 'none', + WebkitUserSelect: 'none', + msUserSelect: 'none', + MozUserSelect: 'none', + cursor: 'pointer', +}; + +const folderContentStyle = { + display: 'inline-block', + clear: 'right', + marginLeft: '5px', + padding: '0', + paddingLeft: '5px', + width: 'auto', +}; + +class Foldable extends React.Component { + constructor(props) { + super(props); + + this.state = { collapsed: true }; + + this.onToggleCallback = this.onToggle.bind(this); + } + + componentDidMount() { + this.refs.folder.style.backgroundColor = '#FFFCE0'; + setTimeout(() => { + this.refs.folder.style.backgroundColor = folderStyle.backgroundColor; + }, 500); + } + + onToggle() { + this.setState({ collapsed: !this.state.collapsed }); + } + + render() { + let content = this.props.children; + + if (this.state.collapsed) { + // return the shortest string representation possible + content = JSON.stringify(JSON.parse(content)); + } + + return ( +
+
+ { this.state.collapsed ? '►' : '▼' } +
+ +
+ { content } +
+
+ ); + } +} + +Foldable.propTypes = { + children: React.PropTypes.string, +}; + +export default Foldable; From 113cd98c585725913e309e7f79daf85078dd42b6 Mon Sep 17 00:00:00 2001 From: Benjamin Kagia Date: Thu, 14 Apr 2016 13:28:28 +0300 Subject: [PATCH 2/3] fix padding value --- dist/client/ui/foldable.js | 2 +- src/client/ui/foldable.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/client/ui/foldable.js b/dist/client/ui/foldable.js index f07389022af3..4cc0f0089bc3 100644 --- a/dist/client/ui/foldable.js +++ b/dist/client/ui/foldable.js @@ -59,7 +59,7 @@ var folderContentStyle = { display: 'inline-block', clear: 'right', marginLeft: '5px', - padding: '0', + padding: '0px', paddingLeft: '5px', width: 'auto' }; diff --git a/src/client/ui/foldable.js b/src/client/ui/foldable.js index 85137b1299be..673e448a9047 100644 --- a/src/client/ui/foldable.js +++ b/src/client/ui/foldable.js @@ -25,7 +25,7 @@ const folderContentStyle = { display: 'inline-block', clear: 'right', marginLeft: '5px', - padding: '0', + padding: '0px', paddingLeft: '5px', width: 'auto', }; From 739f26c4ad8d54dd5a31d7e602e680feea48e70d Mon Sep 17 00:00:00 2001 From: Benjamin Kagia Date: Sun, 17 Apr 2016 10:32:36 +0300 Subject: [PATCH 3/3] add ui tests for foldable component --- dist/client/client_api.js | 5 ++- dist/client/ui/action_logger.js | 10 ++---- dist/client/ui/admin.js | 9 +---- dist/client/ui/foldable.js | 33 ++++++++++++------ src/client/__tests__/client_api.js | 3 ++ src/client/client_api.js | 5 ++- src/client/ui/__tests__/action_logger.js | 16 ++++++--- src/client/ui/__tests__/foldable.js | 43 ++++++++++++++++++++++++ src/client/ui/action_logger.js | 10 +++--- src/client/ui/admin.js | 5 +-- src/client/ui/foldable.js | 25 +++++++++----- 11 files changed, 114 insertions(+), 50 deletions(-) create mode 100644 src/client/ui/__tests__/foldable.js diff --git a/dist/client/client_api.js b/dist/client/client_api.js index 0eb2d67568e5..e228cb52db9e 100644 --- a/dist/client/client_api.js +++ b/dist/client/client_api.js @@ -18,6 +18,8 @@ var _createClass3 = _interopRequireDefault(_createClass2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +var actionIds = 0; + var ClientApi = function () { function ClientApi(_ref) { var syncedStore = _ref.syncedStore; @@ -69,7 +71,8 @@ var ClientApi = function () { args[0] = '[SyntheticEvent]'; } - actions = [{ name: name, args: args }].concat(actions.slice(0, 4)); + var id = actionIds++; + actions = [{ id: id, name: name, args: args }].concat(actions.slice(0, 4)); syncedStore.setData({ actions: actions }); }; } diff --git a/dist/client/ui/action_logger.js b/dist/client/ui/action_logger.js index 8010ad928912..e950cebf4daa 100644 --- a/dist/client/ui/action_logger.js +++ b/dist/client/ui/action_logger.js @@ -78,12 +78,8 @@ var ActionLogger = function (_Component) { (0, _createClass3.default)(ActionLogger, [{ key: 'getActionData', value: function getActionData() { - return this.props.actionLogs.map(function (action, i) { - return _react2.default.createElement( - _foldable2.default, - { key: i }, - action - ); + return this.props.actions.map(function (action) { + return _react2.default.createElement(_foldable2.default, { key: action.id, action: action }); }); } }, { @@ -117,7 +113,7 @@ var ActionLogger = function (_Component) { ActionLogger.propTypes = { onClear: _react2.default.PropTypes.func, - actionLogs: _react2.default.PropTypes.array + actions: _react2.default.PropTypes.array }; exports.default = ActionLogger; \ No newline at end of file diff --git a/dist/client/ui/admin.js b/dist/client/ui/admin.js index ad9a9a170c37..524c2aa77b15 100644 --- a/dist/client/ui/admin.js +++ b/dist/client/ui/admin.js @@ -22,10 +22,6 @@ var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); -var _jsonStringifySafe = require('json-stringify-safe'); - -var _jsonStringifySafe2 = _interopRequireDefault(_jsonStringifySafe); - var _controls = require('./controls'); var _controls2 = _interopRequireDefault(_controls); @@ -102,10 +98,7 @@ function getActionLogger(data) { var _data$actions = data.actions; var actions = _data$actions === undefined ? [] : _data$actions; - var logs = actions.map(function (action) { - return (0, _jsonStringifySafe2.default)(action, null, 2); - }); - return _react2.default.createElement(_action_logger2.default, { actionLogs: logs, onClear: clearLogs }); + return _react2.default.createElement(_action_logger2.default, { actions: actions, onClear: clearLogs }); } function renderMain(data) { diff --git a/dist/client/ui/foldable.js b/dist/client/ui/foldable.js index 4cc0f0089bc3..6e1bf9379c67 100644 --- a/dist/client/ui/foldable.js +++ b/dist/client/ui/foldable.js @@ -4,9 +4,9 @@ Object.defineProperty(exports, "__esModule", { value: true }); -var _stringify = require('babel-runtime/core-js/json/stringify'); +var _extends2 = require('babel-runtime/helpers/extends'); -var _stringify2 = _interopRequireDefault(_stringify); +var _extends3 = _interopRequireDefault(_extends2); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); @@ -32,6 +32,10 @@ var _react = require('react'); var _react2 = _interopRequireDefault(_react); +var _jsonStringifySafe = require('json-stringify-safe'); + +var _jsonStringifySafe2 = _interopRequireDefault(_jsonStringifySafe); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var folderStyle = { @@ -72,8 +76,9 @@ var Foldable = function (_React$Component) { var _this = (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(Foldable).call(this, props)); - _this.state = { collapsed: true }; - + _this.state = { + collapsed: true + }; _this.onToggleCallback = _this.onToggle.bind(_this); return _this; } @@ -96,11 +101,15 @@ var Foldable = function (_React$Component) { }, { key: 'render', value: function render() { - var content = this.props.children; + var action = (0, _extends3.default)({}, this.props.action); + delete action.id; + var content = void 0; if (this.state.collapsed) { // return the shortest string representation possible - content = (0, _stringify2.default)(JSON.parse(content)); + content = (0, _jsonStringifySafe2.default)(action); + } else { + content = (0, _jsonStringifySafe2.default)(action, null, 2); } return _react2.default.createElement( @@ -108,12 +117,16 @@ var Foldable = function (_React$Component) { { ref: 'folder', style: folderStyle }, _react2.default.createElement( 'div', - { style: folderSidebarStyle, onClick: this.onToggleCallback }, - this.state.collapsed ? '►' : '▼' + { style: folderSidebarStyle }, + _react2.default.createElement( + 'span', + { className: 'foldable-toggle', onClick: this.onToggleCallback }, + this.state.collapsed ? '►' : '▼' + ) ), _react2.default.createElement( 'div', - { style: folderContentStyle }, + { className: 'foldable-content', style: folderContentStyle }, content ) ); @@ -123,7 +136,7 @@ var Foldable = function (_React$Component) { }(_react2.default.Component); Foldable.propTypes = { - children: _react2.default.PropTypes.string + action: _react2.default.PropTypes.object }; exports.default = Foldable; \ No newline at end of file diff --git a/src/client/__tests__/client_api.js b/src/client/__tests__/client_api.js index 38713d9407b6..1b4a0913bfff 100644 --- a/src/client/__tests__/client_api.js +++ b/src/client/__tests__/client_api.js @@ -88,6 +88,7 @@ describe('client.ClientApi', () => { { name: 'hello', args: [10, 20], + id: 0, }, ]); }); @@ -107,6 +108,7 @@ describe('client.ClientApi', () => { { name: 'hello', args: [10, 20], + id: 1, }, 50, 40, @@ -132,6 +134,7 @@ describe('client.ClientApi', () => { { name: 'hello', args: ['[SyntheticEvent]'], + id: 2, }, ]); }); diff --git a/src/client/client_api.js b/src/client/client_api.js index 2cf80fbc2ea6..2de412ae4719 100644 --- a/src/client/client_api.js +++ b/src/client/client_api.js @@ -1,3 +1,5 @@ +let actionIds = 0; + export default class ClientApi { constructor({ syncedStore, storyStore }) { this._syncedStore = syncedStore; @@ -34,7 +36,8 @@ export default class ClientApi { args[0] = '[SyntheticEvent]'; } - actions = [{ name, args }].concat(actions.slice(0, 4)); + const id = actionIds++; + actions = [{ id, name, args }].concat(actions.slice(0, 4)); syncedStore.setData({ actions }); }; } diff --git a/src/client/ui/__tests__/action_logger.js b/src/client/ui/__tests__/action_logger.js index bfadde9f4e95..1ba781eda8d8 100644 --- a/src/client/ui/__tests__/action_logger.js +++ b/src/client/ui/__tests__/action_logger.js @@ -8,23 +8,29 @@ import ActionLogger from '../action_logger'; describe('', function () { describe('render', function () { it('should render logs - empty', function () { - const wrap = shallow(); + const wrap = shallow(); const logs = wrap.find('pre').first(); expect(logs.text()).to.equal(''); }); it('should render logs', function () { - const data = ['a1', 'a2', 'a3']; - const wrap = shallow(); + const action1 = { name: 'a1' }; + const action2 = { name: 'a2' }; + const action3 = { name: 'a3' }; + + const data = [action1, action2, action3]; + + const wrap = shallow(); const logs = wrap.find('pre').first(); - expect(logs.text()).to.equal('a1a2a3'); + + expect(logs.children().length).to.equal(3); }); }); describe('functions', function () { it('should call the onClear prop when the button is clicked', function () { const onClear = sinon.spy(); - const wrap = shallow(); + const wrap = shallow(); const clear = wrap.find('button').first(); clear.simulate('click'); expect(onClear.calledOnce).to.equal(true); diff --git a/src/client/ui/__tests__/foldable.js b/src/client/ui/__tests__/foldable.js new file mode 100644 index 000000000000..9b7a8b2ae85c --- /dev/null +++ b/src/client/ui/__tests__/foldable.js @@ -0,0 +1,43 @@ +const { describe, it } = global; +import { expect } from 'chai'; +import { shallow } from 'enzyme'; +import React from 'react'; +import Foldable from '../foldable'; + +describe('', function () { + describe('render', function () { + it('should render action compact by default', function () { + const data = { + name: 'test action', + args: 'things', + }; + + const compactString = '{"name":"test action","args":"things"}'; + + const wrap = shallow(); + const content = wrap.find('.foldable-content').first(); + + expect(content.text()).to.equal(compactString); + }); + + it('should render action in full when unfolded', function () { + const data = { + name: 'test action', + args: 'things', + }; + + const fullString = '{ "name": "test action",\n "args": "things"\n}'; + + const wrap = shallow(); + const toggle = wrap.find('.foldable-toggle').first(); + + toggle.simulate('click'); + + expect(wrap.state()).to.deep.equal({ collapsed: false }); + + const content = wrap.find('.foldable-content').first(); + + expect(content.text()).to.equal(fullString); + }); + }); +}); diff --git a/src/client/ui/action_logger.js b/src/client/ui/action_logger.js index 40d3baacb8e8..e787a3b99884 100644 --- a/src/client/ui/action_logger.js +++ b/src/client/ui/action_logger.js @@ -40,10 +40,10 @@ const btnStyle = { class ActionLogger extends Component { getActionData() { - return this.props.actionLogs - .map((action, i) => { - return { action }; - }); + return this.props.actions + .map((action) => { + return (); + }); } render() { @@ -62,7 +62,7 @@ class ActionLogger extends Component { ActionLogger.propTypes = { onClear: React.PropTypes.func, - actionLogs: React.PropTypes.array, + actions: React.PropTypes.array, }; export default ActionLogger; diff --git a/src/client/ui/admin.js b/src/client/ui/admin.js index 9e324e46da25..c2f9fc831666 100644 --- a/src/client/ui/admin.js +++ b/src/client/ui/admin.js @@ -1,6 +1,5 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import stringify from 'json-stringify-safe'; import StorybookControls from './controls'; import ActionLogger from './action_logger'; import Layout from './layout'; @@ -67,9 +66,7 @@ export function getIframe(data) { export function getActionLogger(data) { const { actions = [] } = data; - const logs = actions - .map((action) => stringify(action, null, 2)); - return (); + return (); } export function renderMain(data) { diff --git a/src/client/ui/foldable.js b/src/client/ui/foldable.js index 673e448a9047..cc227f7edbf6 100644 --- a/src/client/ui/foldable.js +++ b/src/client/ui/foldable.js @@ -1,4 +1,5 @@ import React from 'react'; +import stringify from 'json-stringify-safe'; const folderStyle = { display: 'block', @@ -33,9 +34,9 @@ const folderContentStyle = { class Foldable extends React.Component { constructor(props) { super(props); - - this.state = { collapsed: true }; - + this.state = { + collapsed: true, + }; this.onToggleCallback = this.onToggle.bind(this); } @@ -51,20 +52,26 @@ class Foldable extends React.Component { } render() { - let content = this.props.children; + const action = { ...this.props.action }; + delete action.id; + let content; if (this.state.collapsed) { // return the shortest string representation possible - content = JSON.stringify(JSON.parse(content)); + content = stringify(action); + } else { + content = stringify(action, null, 2); } return (
-
- { this.state.collapsed ? '►' : '▼' } +
+ + { this.state.collapsed ? '►' : '▼' } +
-
+
{ content }
@@ -73,7 +80,7 @@ class Foldable extends React.Component { } Foldable.propTypes = { - children: React.PropTypes.string, + action: React.PropTypes.object, }; export default Foldable;