mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-02-22 22:42:54 +03:00
Ugly code inc, only a draft; timeline-view and horizontal scrolling
This commit is contained in:
parent
dffe84ec04
commit
dba48a6128
|
@ -1,6 +1,9 @@
|
||||||
import React, { PropTypes, findDOMNode } from 'react';
|
import React, { PropTypes, findDOMNode } from 'react';
|
||||||
import LogMonitorEntry from './LogMonitorEntry';
|
import LogMonitorEntry from './LogMonitorEntry';
|
||||||
|
|
||||||
|
if(BROWSER){
|
||||||
|
require("./timeline.less");
|
||||||
|
}
|
||||||
export default class LogMonitor {
|
export default class LogMonitor {
|
||||||
constructor() {
|
constructor() {
|
||||||
window.addEventListener('keydown', ::this.handleKeyPress);
|
window.addEventListener('keydown', ::this.handleKeyPress);
|
||||||
|
@ -135,7 +138,9 @@ export default class LogMonitor {
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{elements}
|
<ul className="timeline">
|
||||||
|
{elements}
|
||||||
|
</ul>
|
||||||
<div>
|
<div>
|
||||||
{computedStates.length > 1 &&
|
{computedStates.length > 1 &&
|
||||||
<a onClick={::this.handleRollback}
|
<a onClick={::this.handleRollback}
|
||||||
|
|
|
@ -1,82 +1,142 @@
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes, Component } from 'react';
|
||||||
|
|
||||||
function hsvToRgb(h, s, v) {
|
function hsvToRgb(h, s, v) {
|
||||||
const i = Math.floor(h);
|
const i = Math.floor(h);
|
||||||
const f = h - i;
|
const f = h - i;
|
||||||
const p = v * (1 - s);
|
const p = v * (1 - s);
|
||||||
const q = v * (1 - f * s);
|
const q = v * (1 - f * s);
|
||||||
const t = v * (1 - (1 - f) * s);
|
const t = v * (1 - (1 - f) * s);
|
||||||
const mod = i % 6;
|
const mod = i % 6;
|
||||||
const r = [v, q, p, p, t, v][mod];
|
const r = [v, q, p, p, t, v][mod];
|
||||||
const g = [t, v, v, q, p, p][mod];
|
const g = [t, v, v, q, p, p][mod];
|
||||||
const b = [p, p, t, v, v, q][mod];
|
const b = [p, p, t, v, v, q][mod];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
r: Math.round(r * 255),
|
r: Math.round(r * 255),
|
||||||
g: Math.round(g * 255),
|
g: Math.round(g * 255),
|
||||||
b: Math.round(b * 255)
|
b: Math.round(b * 255)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function colorFromString(token) {
|
function colorFromString(token) {
|
||||||
const splitToken = token.split('');
|
const splitToken = token.split('');
|
||||||
const finalToken = splitToken.concat(splitToken.reverse());
|
const finalToken = splitToken.concat(splitToken.reverse());
|
||||||
|
|
||||||
const number = finalToken.reduce(
|
const number = finalToken.reduce(
|
||||||
(sum, char) => sum + char.charCodeAt(0),
|
(sum, char) => sum + char.charCodeAt(0),
|
||||||
0
|
0
|
||||||
) * Math.abs(Math.sin(token.length));
|
) * Math.abs(Math.sin(token.length));
|
||||||
|
|
||||||
const h = Math.round((number * (180 / Math.PI) * token.length) % 360);
|
const h = Math.round((number * (180 / Math.PI) * token.length) % 360);
|
||||||
const s = number % 100 / 100;
|
const s = number % 100 / 100;
|
||||||
const v = 1;
|
const v = 1;
|
||||||
|
|
||||||
return hsvToRgb(h, s, v);
|
return hsvToRgb(h, s, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class LogMonitorEntry {
|
export default class LogMonitorEntry extends Component {
|
||||||
static propTypes = {
|
constructor(props) {
|
||||||
index: PropTypes.number.isRequired,
|
super(props);
|
||||||
state: PropTypes.object.isRequired,
|
this.state = {
|
||||||
action: PropTypes.object.isRequired,
|
maximized: false,
|
||||||
select: PropTypes.func.isRequired,
|
time: new Date()
|
||||||
error: PropTypes.string,
|
|
||||||
onActionClick: PropTypes.func.isRequired,
|
|
||||||
collapsed: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
printState(state, error) {
|
|
||||||
let errorText = error;
|
|
||||||
if (!errorText) {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(this.props.select(state), null, 2);
|
|
||||||
} catch (err) {
|
|
||||||
errorText = 'Error selecting state.';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
static propTypes = {
|
||||||
<span style={{
|
index: PropTypes.number.isRequired,
|
||||||
|
state: PropTypes.object.isRequired,
|
||||||
|
action: PropTypes.object.isRequired,
|
||||||
|
select: PropTypes.func.isRequired,
|
||||||
|
error: PropTypes.string,
|
||||||
|
onActionClick: PropTypes.func.isRequired,
|
||||||
|
collapsed: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
printState(state, error) {
|
||||||
|
let errorText = error;
|
||||||
|
if (!errorText) {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(this.props.select(state), null, 2);
|
||||||
|
} catch (err) {
|
||||||
|
errorText = 'Error selecting state.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span style={{
|
||||||
fontStyle: 'italic'
|
fontStyle: 'italic'
|
||||||
}}>
|
}}>
|
||||||
({errorText})
|
({errorText})
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleActionClick() {
|
handleCollapseClick() {
|
||||||
const { index, onActionClick } = this.props;
|
const { index, onActionClick } = this.props;
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
onActionClick(index);
|
onActionClick(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
handleMaximizeClick() {
|
||||||
const { index, error, action, state, collapsed } = this.props;
|
const { maximized } = this.state;
|
||||||
const { r, g, b } = colorFromString(action.type);
|
this.setState({maximized: !maximized});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
render() {
|
||||||
<pre style={{
|
const { index, error, action, state, collapsed } = this.props;
|
||||||
|
const { maximized, time } = this.state;
|
||||||
|
if (!action.type) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { r, g, b } = colorFromString(action.type);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className="timeline-inverted">
|
||||||
|
<a className="timeline-badge" onClick={::this.handleCollapseClick} href="javascript:;">
|
||||||
|
<i className={"fa fa-" + (collapsed ? "ban" : "check")}></i>
|
||||||
|
</a>
|
||||||
|
{maximized ?
|
||||||
|
<div className="timeline-panel" style={{whiteSpace: "nowrap", overflow: "auto"}}>
|
||||||
|
<div style={{display: "table", tableLayout: "fixed", width: "100%"}}>
|
||||||
|
<div style={{ width: "400px", display: "table-cell"}}>
|
||||||
|
<a className="timeline-heading" onClick={::this.handleMaximizeClick} href="javascript:;">
|
||||||
|
<pre className="timeline-pre">
|
||||||
|
<a style={{color: `rgb(${r}, ${g}, ${b})`,}}>
|
||||||
|
<i className="fa fa-minus-square"></i>
|
||||||
|
{JSON.stringify(action, null, 2)}
|
||||||
|
</a>
|
||||||
|
</pre>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div className="timeline-body">
|
||||||
|
<pre className="timeline-pre">
|
||||||
|
{this.printState(state, error)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> :
|
||||||
|
<div className="timeline-panel">
|
||||||
|
<a class="timeline-heading" onClick={::this.handleMaximizeClick} href="javascript:;">
|
||||||
|
<h4 class="timeline-title" style={{color: `rgb(${r}, ${g}, ${b})`,}}>
|
||||||
|
<i className="fa fa-plus-square"></i>
|
||||||
|
{" " + action.type}
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
<small className="text-muted">
|
||||||
|
<i className="fa fa-clock-o"></i>
|
||||||
|
{" " + getTimestring(time)}
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<pre style={{
|
||||||
textDecoration: collapsed ? 'line-through' : 'none',
|
textDecoration: collapsed ? 'line-through' : 'none',
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
border: "0px"
|
border: "0px"
|
||||||
|
@ -92,34 +152,48 @@ export default class LogMonitorEntry {
|
||||||
cursor: (index > 0) ? 'pointer' : 'default',
|
cursor: (index > 0) ? 'pointer' : 'default',
|
||||||
WebkitUserSelect: 'none'
|
WebkitUserSelect: 'none'
|
||||||
}}>
|
}}>
|
||||||
{JSON.stringify(action, null, 2)}
|
{JSON.stringify(action, null, 2)}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{!collapsed &&
|
{!collapsed &&
|
||||||
<p style={{
|
<p style={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
transform: 'rotate(180deg)',
|
transform: 'rotate(180deg)',
|
||||||
color: 'lightyellow',
|
color: 'lightyellow',
|
||||||
fontSize: "2em"
|
fontSize: "2em"
|
||||||
}}>
|
}}>
|
||||||
⇧
|
⇧
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
{!collapsed &&
|
{!collapsed &&
|
||||||
<div style={{
|
<div style={{
|
||||||
paddingBottom: '1em',
|
paddingBottom: '1em',
|
||||||
paddingTop: '1em',
|
paddingTop: '1em',
|
||||||
color: 'lightyellow'
|
color: 'lightyellow'
|
||||||
}}>
|
}}>
|
||||||
{this.printState(state, error)}
|
{this.printState(state, error)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<hr style={{
|
<hr style={{
|
||||||
marginBottom: '2em'
|
marginBottom: '2em'
|
||||||
}} />
|
}}/>
|
||||||
</pre>
|
</pre>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
function getTimestring(d) {
|
||||||
|
var x = document.getElementById("demo");
|
||||||
|
var h = addZero(d.getHours(), 2);
|
||||||
|
var m = addZero(d.getMinutes(), 2);
|
||||||
|
var s = addZero(d.getSeconds(), 2);
|
||||||
|
var ms = addZero(d.getMilliseconds(), 3);
|
||||||
|
return h + ":" + m + ":" + s + ":" + ms;
|
||||||
|
}
|
||||||
|
function addZero(x, n) {
|
||||||
|
if (x.toString().length < n) {
|
||||||
|
x = "0" + x;
|
||||||
|
}
|
||||||
|
return x;
|
||||||
}
|
}
|
||||||
|
|
191
src/react/timeline.less
Normal file
191
src/react/timeline.less
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
.timeline {
|
||||||
|
list-style: none;
|
||||||
|
padding: 20px 0 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-pre {
|
||||||
|
background-color: transparent;
|
||||||
|
color: white;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline:before {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
content: " ";
|
||||||
|
width: 3px;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
left: 20px;
|
||||||
|
margin-left: -1.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline > li {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline > li:before,
|
||||||
|
.timeline > li:after {
|
||||||
|
content: " ";
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline > li:after {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline > li:before,
|
||||||
|
.timeline > li:after {
|
||||||
|
content: " ";
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline > li:after {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline > li > .timeline-panel {
|
||||||
|
width: 80%;
|
||||||
|
float: left;
|
||||||
|
border: 1px solid #d4d4d4;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 5px;
|
||||||
|
position: relative;
|
||||||
|
-webkit-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.175);
|
||||||
|
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.175);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline > li > .timeline-panel:before {
|
||||||
|
position: absolute;
|
||||||
|
top: 26px;
|
||||||
|
right: -15px;
|
||||||
|
display: inline-block;
|
||||||
|
border-top: 15px solid transparent;
|
||||||
|
border-left: 15px solid #ccc;
|
||||||
|
border-right: 0 solid #ccc;
|
||||||
|
border-bottom: 15px solid transparent;
|
||||||
|
content: " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline > li > .timeline-panel:after {
|
||||||
|
position: absolute;
|
||||||
|
top: 27px;
|
||||||
|
right: -14px;
|
||||||
|
display: inline-block;
|
||||||
|
border-top: 14px solid transparent;
|
||||||
|
border-left: 14px solid #fff;
|
||||||
|
border-right: 0 solid #fff;
|
||||||
|
border-bottom: 14px solid transparent;
|
||||||
|
content: " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline > li > .timeline-badge {
|
||||||
|
color: #fff;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
font-size: 1.4em;
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
left: 20px;
|
||||||
|
margin-left: -25px;
|
||||||
|
background-color: #999999;
|
||||||
|
z-index: 100;
|
||||||
|
border-top-right-radius: 50%;
|
||||||
|
border-top-left-radius: 50%;
|
||||||
|
border-bottom-right-radius: 50%;
|
||||||
|
border-bottom-left-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline > li.timeline-inverted > .timeline-panel {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline > li.timeline-inverted > .timeline-panel:before {
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 15px;
|
||||||
|
left: -15px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline > li.timeline-inverted > .timeline-panel:after {
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 14px;
|
||||||
|
left: -14px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-badge.primary {
|
||||||
|
background-color: #2e6da4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-badge.success {
|
||||||
|
background-color: #3f903f !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-badge.warning {
|
||||||
|
background-color: #f0ad4e !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-badge.danger {
|
||||||
|
background-color: #d9534f !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-badge.info {
|
||||||
|
background-color: #5bc0de !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-title {
|
||||||
|
margin-top: 0;
|
||||||
|
//color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-body > p,
|
||||||
|
.timeline-body > ul {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-body > p + p {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
ul.timeline:before {
|
||||||
|
left: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.timeline > li > .timeline-panel {
|
||||||
|
width: calc(100% - 90px);
|
||||||
|
width: -moz-calc(100% - 90px);
|
||||||
|
width: -webkit-calc(100% - 90px);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.timeline > li > .timeline-badge {
|
||||||
|
left: 15px;
|
||||||
|
margin-left: 0;
|
||||||
|
top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.timeline > li > .timeline-panel {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.timeline > li > .timeline-panel:before {
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 15px;
|
||||||
|
left: -15px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.timeline > li > .timeline-panel:after {
|
||||||
|
border-left-width: 0;
|
||||||
|
border-right-width: 14px;
|
||||||
|
left: -14px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user