added theming

This commit is contained in:
dzannotti 2015-08-07 17:36:36 +01:00
parent 50212f88a0
commit 42d4c3f890
17 changed files with 165 additions and 131 deletions

View File

@ -15,7 +15,6 @@ export function getDefaultStyle(props) {
fontSize: 17, fontSize: 17,
overflow: 'hidden', overflow: 'hidden',
opacity: 0.92, opacity: 0.92,
background: '#181d20',
color: 'white', color: 'white',
left: left ? 0 : undefined, left: left ? 0 : undefined,
right: right ? 0 : undefined, right: right ? 0 : undefined,

View File

@ -15,17 +15,12 @@ const styles = {
label: { label: {
margin: 0, margin: 0,
padding: 0, padding: 0,
display: 'inline-block', display: 'inline-block'
color: '#8fa1b2'
}, },
span: { span: {
color: '#C042DF',
fontSize: 12, fontSize: 12,
cursor: 'default' cursor: 'default'
}, },
spanExpanded: {
color: '#D7D5D8'
},
spanType: { spanType: {
marginLeft: 5, marginLeft: 5,
marginRight: 5, marginRight: 5,
@ -64,7 +59,7 @@ export default class JSONArrayNode extends React.Component {
let childNodes = []; let childNodes = [];
if (this.state.expanded && this.needsChildNodes) { if (this.state.expanded && this.needsChildNodes) {
this.props.data.forEach((element, idx) => { this.props.data.forEach((element, idx) => {
childNodes.push(grabNode(idx, element)); childNodes.push(grabNode(idx, element, this.props.theme));
}); });
this.needsChildNodes = false; this.needsChildNodes = false;
this.renderedChildren = childNodes; this.renderedChildren = childNodes;
@ -89,18 +84,26 @@ export default class JSONArrayNode extends React.Component {
listStyle: 'none', listStyle: 'none',
display: (this.state.expanded) ? 'block' : 'none' display: (this.state.expanded) ? 'block' : 'none'
}; };
let containerStyle = {...styles.base, ...styles.parentNode}; let containerStyle = { ...styles.base, ...styles.parentNode };
let spanStyle = { ...styles.span }; let spanStyle = {
...styles.span,
color: this.props.theme.base0E
};
if (this.state.expanded) { if (this.state.expanded) {
spanStyle = { spanStyle = {
...spanStyle, ...spanStyle,
...styles.spanExpanded color: this.props.theme.base05
}; };
} }
return ( return (
<li style={containerStyle} onClick={::this.handleClick}> <li style={containerStyle} onClick={::this.handleClick}>
<JSONArrow open={this.state.expanded}/> <JSONArrow theme={this.props.theme} open={this.state.expanded}/>
<label style={styles.label}>{this.props.keyName}:</label> <label style={{
...styles.label,
color: this.props.theme.base0D
}}>
{this.props.keyName}:
</label>
<span style={spanStyle}> <span style={spanStyle}>
<span style={styles.spanType}>[]</span> <span style={styles.spanType}>[]</span>
{this.getItemString()} {this.getItemString()}

View File

@ -11,7 +11,8 @@ const styles = {
MozTransition: '150ms', MozTransition: '150ms',
borderLeft: '5px solid transparent', borderLeft: '5px solid transparent',
borderRight: '5px solid transparent', borderRight: '5px solid transparent',
borderTop: '5px solid #39ace6', borderTopWidth: 5,
borderTopStyle: 'solid',
WebkitTransform: 'rotateZ(-90deg)', WebkitTransform: 'rotateZ(-90deg)',
MozTransform: 'rotateZ(-90deg)', MozTransform: 'rotateZ(-90deg)',
transform: 'rotateZ(-90deg)' transform: 'rotateZ(-90deg)'
@ -25,7 +26,10 @@ const styles = {
export default class JSONArrow extends React.Component { export default class JSONArrow extends React.Component {
render() { render() {
let style = { ...styles.base }; let style = {
...styles.base,
borderTopColor: this.props.theme.base0C
};
if (this.props.open) { if (this.props.open) {
style = { style = {
...style, ...style,

View File

@ -12,11 +12,7 @@ const styles = {
}, },
label: { label: {
display: 'inline-block', display: 'inline-block',
marginRight: 5, marginRight: 5
color: '#8fa1b2'
},
value: {
color: '#08c6eE'
} }
}; };
@ -26,8 +22,13 @@ export default class JSONBooleanNode extends React.Component {
const truthString = (this.props.value) ? 'true' : 'false'; const truthString = (this.props.value) ? 'true' : 'false';
return ( return (
<li style={styles.base} onClick={::this.handleClick}> <li style={styles.base} onClick={::this.handleClick}>
<label style={styles.label}>{this.props.keyName}:</label> <label style={{
<span style={styles.value}>{truthString}</span> ...styles.label,
color: this.props.theme.base0D
}}>
{this.props.keyName}:
</label>
<span style={{ color: this.props.theme.base0F }}>{truthString}</span>
</li> </li>
); );
} }

View File

@ -12,11 +12,7 @@ const styles = {
}, },
label: { label: {
display: 'inline-block', display: 'inline-block',
marginRight: 5, marginRight: 5
color: '#8fa1b2'
},
value: {
color: '#DF113A'
} }
}; };
@ -25,8 +21,13 @@ export default class JSONNullNode extends React.Component {
render() { render() {
return ( return (
<li style={styles.base} onClick={::this.handleClick}> <li style={styles.base} onClick={::this.handleClick}>
<label style={styles.label}>{this.props.keyName}:</label> <label style={{
<span style={styles.value}>null</span> ...styles.label,
color: this.props.theme.base0D
}}>
{this.props.keyName}:
</label>
<span style={{ color: this.props.theme.base08 }}>null</span>
</li> </li>
); );
} }

View File

@ -12,11 +12,7 @@ const styles = {
}, },
label: { label: {
display: 'inline-block', display: 'inline-block',
marginRight: 5, marginRight: 5
color: '#8fa1b2'
},
value: {
color: '#0B75F5'
} }
}; };
@ -25,8 +21,13 @@ export default class JSONNumberNode extends React.Component {
render() { render() {
return ( return (
<li style={styles.base} onClick={::this.handleClick}> <li style={styles.base} onClick={::this.handleClick}>
<label style={styles.label}>{this.props.keyName}:</label> <label style={{
<span style={styles.value}>{this.props.value}</span> ...styles.label,
color: this.props.theme.base0D
}}>
{this.props.keyName}:
</label>
<span style={{ color: this.props.theme.base09 }}>{this.props.value}</span>
</li> </li>
); );
} }

View File

@ -15,17 +15,12 @@ const styles = {
label: { label: {
margin: 0, margin: 0,
padding: 0, padding: 0,
display: 'inline-block', display: 'inline-block'
color: '#8fa1b2'
}, },
span: { span: {
color: '#049977',
fontSize: 12, fontSize: 12,
cursor: 'default' cursor: 'default'
}, },
spanExpanded: {
color: '#D7D5D8'
},
spanType: { spanType: {
marginLeft: 5, marginLeft: 5,
marginRight: 5, marginRight: 5,
@ -65,7 +60,7 @@ export default class JSONObjectNode extends React.Component {
let childNodes = []; let childNodes = [];
for (let k in obj) { for (let k in obj) {
if (obj.hasOwnProperty(k)) { if (obj.hasOwnProperty(k)) {
childNodes.push(grabNode(k, obj[k])); childNodes.push(grabNode(k, obj[k], this.props.theme));
} }
} }
this.needsChildNodes = false; this.needsChildNodes = false;
@ -91,18 +86,26 @@ export default class JSONObjectNode extends React.Component {
listStyle: 'none', listStyle: 'none',
display: (this.state.expanded) ? 'block' : 'none' display: (this.state.expanded) ? 'block' : 'none'
}; };
let containerStyle = {...styles.base, ...styles.parentNode}; let containerStyle = { ...styles.base, ...styles.parentNode };
let spanStyle = { ...styles.span }; let spanStyle = {
...styles.span,
color: this.props.theme.base0B
};
if (this.state.expanded) { if (this.state.expanded) {
spanStyle = { spanStyle = {
...spanStyle, ...spanStyle,
...styles.spanExpanded color: this.props.theme.base05
}; };
} }
return ( return (
<li style={containerStyle} onClick={::this.handleClick}> <li style={containerStyle} onClick={::this.handleClick}>
<JSONArrow open={this.state.expanded}/> <JSONArrow theme={this.props.theme} open={this.state.expanded}/>
<label style={styles.label}>{this.props.keyName}:</label> <label style={{
...styles.label,
color: this.props.theme.base0D
}}>
{this.props.keyName}:
</label>
<span style={spanStyle}> <span style={spanStyle}>
<span style={styles.spanType}>&#123;&#125;</span> <span style={styles.spanType}>&#123;&#125;</span>
{this.getItemString()} {this.getItemString()}

View File

@ -12,11 +12,7 @@ const styles = {
}, },
label: { label: {
display: 'inline-block', display: 'inline-block',
marginRight: 5, marginRight: 5
color: '#8fa1b2'
},
value: {
color: '#717c93'
} }
}; };
@ -25,8 +21,13 @@ export default class JSONStringNode extends React.Component {
render() { render() {
return ( return (
<li style={styles.base} onClick={::this.handleClick}> <li style={styles.base} onClick={::this.handleClick}>
<label style={styles.label}>{this.props.keyName}:</label> <label style={{
<span style={styles.value}>{this.props.value}</span> ...styles.label,
color: this.props.theme.base0D
}}>
{this.props.keyName}:
</label>
<span style={{ color: this.props.theme.base0B }}>{this.props.value}</span>
</li> </li>
); );
} }

View File

@ -7,21 +7,21 @@ import JSONNumberNode from './JSONNumberNode';
import JSONBooleanNode from './JSONBooleanNode'; import JSONBooleanNode from './JSONBooleanNode';
import JSONNullNode from './JSONNullNode'; import JSONNullNode from './JSONNullNode';
export default function(key, value) { export default function(key, value, theme) {
const nodeType = objType(value); const nodeType = objType(value);
const aKey = key + Date.now(); const aKey = key + Date.now();
if (nodeType === 'Object') { if (nodeType === 'Object') {
return <JSONObjectNode data={value} keyName={key} key={aKey} />; return <JSONObjectNode data={value} theme={theme} keyName={key} key={aKey} />;
} else if (nodeType === 'Array') { } else if (nodeType === 'Array') {
return <JSONArrayNode data={value} keyName={key} key={aKey} />; return <JSONArrayNode data={value} theme={theme} keyName={key} key={aKey} />;
} else if (nodeType === 'String') { } else if (nodeType === 'String') {
return <JSONStringNode keyName={key} value={value} key={aKey} />; return <JSONStringNode keyName={key} theme={theme} value={value} key={aKey} />;
} else if (nodeType === 'Number') { } else if (nodeType === 'Number') {
return <JSONNumberNode keyName={key} value={value} key={aKey} />; return <JSONNumberNode keyName={key} theme={theme} value={value} key={aKey} />;
} else if (nodeType === 'Boolean') { } else if (nodeType === 'Boolean') {
return <JSONBooleanNode keyName={key} value={value} key={aKey} />; return <JSONBooleanNode keyName={key} theme={theme} value={value} key={aKey} />;
} else if (nodeType === 'Null') { } else if (nodeType === 'Null') {
return <JSONNullNode keyName={key} value={value} key={aKey} />; return <JSONNullNode keyName={key} theme={theme} value={value} key={aKey} />;
} }
console.error('Unknown node type:', nodeType); console.error('Unknown node type:', nodeType);
return false; return false;

View File

@ -38,9 +38,9 @@ export default class JSONTree extends React.Component {
let rootNode = false; let rootNode = false;
const keyName = this.props.keyName || 'root'; const keyName = this.props.keyName || 'root';
if (nodeType === 'Object') { if (nodeType === 'Object') {
rootNode = <JSONObjectNode data={this.props.data} keyName={keyName} initialExpanded={true} />; rootNode = <JSONObjectNode theme={this.props.theme} data={this.props.data} keyName={keyName} initialExpanded={true} />;
} else if (nodeType === 'Array') { } else if (nodeType === 'Array') {
rootNode = <JSONArrayNode data={this.props.data} initialExpanded={true} keyName={keyName} />; rootNode = <JSONArrayNode theme={this.props.theme} data={this.props.data} initialExpanded={true} keyName={keyName} />;
} }
return ( return (
<ul style={styles.tree}> <ul style={styles.tree}>

View File

@ -1,26 +1,21 @@
import React, { PropTypes, findDOMNode } from 'react'; import React, { PropTypes, findDOMNode } from 'react';
import LogMonitorEntry from './LogMonitorEntry'; import LogMonitorEntry from './LogMonitorEntry';
import LogMonitorButton from './LogMonitorButton'; import LogMonitorButton from './LogMonitorButton';
import themes from "./themes";
const styles = { const styles = {
container: { container: {
fontFamily: 'monospace', fontFamily: 'monospace',
position: 'relative', position: 'relative',
overflowY: 'hidden' overflowY: 'hidden',
width: '100%',
height: '100%'
}, },
buttonBarWrapper: { buttonBarWrapper: {
backgroundColor: '#343c45',
borderBottom: '1px solid #3f464d',
marginBottom: 1 marginBottom: 1
}, },
buttonBar: { buttonBar: {
paddingLeft: 2 paddingLeft: 2
},
bordering: {
'float': 'left',
height: 1,
border: '1px solid #20262c',
width: '100%'
} }
} }
@ -48,6 +43,7 @@ export default class LogMonitor {
static defaultProps = { static defaultProps = {
select: (state) => state, select: (state) => state,
monitorState: { isVisible: true }, monitorState: { isVisible: true },
theme: 'ocean'
}; };
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
@ -77,7 +73,6 @@ export default class LogMonitor {
if (this.scrollDown) { if (this.scrollDown) {
const scrollableNode = node.parentElement; const scrollableNode = node.parentElement;
const { offsetHeight, scrollHeight } = scrollableNode; const { offsetHeight, scrollHeight } = scrollableNode;
scrollableNode.scrollTop = scrollHeight - offsetHeight; scrollableNode.scrollTop = scrollHeight - offsetHeight;
this.scrollDown = false; this.scrollDown = false;
} }
@ -118,6 +113,17 @@ export default class LogMonitor {
render() { render() {
const elements = []; const elements = [];
const { monitorState, skippedActions, stagedActions, computedStates, select } = this.props; const { monitorState, skippedActions, stagedActions, computedStates, select } = this.props;
let theme;
if (typeof this.props.theme === 'string') {
if (typeof themes[this.props.theme] !== 'undefined') {
theme = themes[this.props.theme]
} else {
console.warn('DevTools theme ' + this.props.theme + ' not found, defaulting to ocean');
theme = themes.ocean
}
} else {
theme = this.props.theme;
}
if (!monitorState.isVisible) { if (!monitorState.isVisible) {
return null; return null;
} }
@ -129,6 +135,7 @@ export default class LogMonitor {
elements.push( elements.push(
<LogMonitorEntry key={i} <LogMonitorEntry key={i}
index={i} index={i}
theme={theme}
select={select} select={select}
action={action} action={action}
state={state} state={state}
@ -139,20 +146,22 @@ export default class LogMonitor {
} }
return ( return (
<div style={styles.container}> <div style={{...styles.container, backgroundColor: theme.base00}}>
<div style={styles.buttonBarWrapper}> <div style={{
<div style={styles.buttonBar}> ...styles.buttonBar,
<LogMonitorButton onClick={::this.handleReset}>Reset</LogMonitorButton> backgroundColor: theme.base01,
{computedStates.length > 1 && borderBottom: theme.base00
<LogMonitorButton onClick={::this.handleRollback}>Revert</LogMonitorButton> }}>
} <LogMonitorButton theme={theme} onClick={::this.handleReset}>Reset</LogMonitorButton>
{Object.keys(skippedActions).some(key => skippedActions[key]) && {computedStates.length > 1 &&
<LogMonitorButton onClick={::this.handleSweep}>Sweep</LogMonitorButton> <LogMonitorButton theme={theme} onClick={::this.handleRollback}>Revert</LogMonitorButton>
} }
{computedStates.length > 1 && {Object.keys(skippedActions).some(key => skippedActions[key]) &&
<LogMonitorButton onClick={::this.handleCommit}>Commit</LogMonitorButton> <LogMonitorButton theme={theme} onClick={::this.handleSweep}>Sweep</LogMonitorButton>
} }
</div> {computedStates.length > 1 &&
<LogMonitorButton theme={theme} onClick={::this.handleCommit}>Commit</LogMonitorButton>
}
</div> </div>
<div styles={{ <div styles={{
overflowX: 'hidden', overflowX: 'hidden',

View File

@ -9,9 +9,6 @@ const styles = {
marginTop: 2, marginTop: 2,
display: 'inline-block', display: 'inline-block',
fontSize: "0.8em" fontSize: "0.8em"
},
active: {
backgroundColor: "#252c33"
} }
} }
@ -47,7 +44,7 @@ export default class LogMonitorButton extends React.Component {
if (this.state.hovered) { if (this.state.hovered) {
style = { style = {
...style, ...style,
...styles.active backgroundColor: this.props.theme.base00
}; };
} }
return ( return (

View File

@ -2,38 +2,12 @@ import React, { PropTypes } from 'react';
import JSONTree from './JSONTree'; import JSONTree from './JSONTree';
import LogMonitorEntryAction from "./LogMonitorEntryAction"; import LogMonitorEntryAction from "./LogMonitorEntryAction";
function hsvToRgb(h, s, v) { function colorFromString(theme, token) {
const i = Math.floor(h);
const f = h - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
const mod = i % 6;
const r = [v, q, p, p, t, v][mod];
const g = [t, v, v, q, p, p][mod];
const b = [p, p, t, v, v, q][mod];
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255)
};
}
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 = (parseInt(finalToken, 36) + finalToken.length) % 8;
const number = finalToken.reduce( const themeNumber = 'base0' + (number + 8).toString(16).toUpperCase();
(sum, char) => sum + char.charCodeAt(0), return theme[themeNumber];
0
) * Math.abs(Math.sin(token.length));
const h = Math.round((number * (180 / Math.PI) * token.length) % 360);
const s = number % 100 / 100;
const v = 1;
return hsvToRgb(h, s, v);
} }
const styles = { const styles = {
@ -58,7 +32,7 @@ export default class LogMonitorEntry {
let errorText = error; let errorText = error;
if (!errorText) { if (!errorText) {
try { try {
return <JSONTree keyName={'state'} data={this.props.select(state)} /> return <JSONTree theme={this.props.theme} keyName={'state'} data={this.props.select(state)} />
} catch (err) { } catch (err) {
errorText = 'Error selecting state.'; errorText = 'Error selecting state.';
} }
@ -82,15 +56,19 @@ export default class LogMonitorEntry {
render() { render() {
const { index, error, action, state, collapsed } = this.props; const { index, error, action, state, collapsed } = this.props;
const { r, g, b } = colorFromString(action.type);
const styleEntry = { const styleEntry = {
opacity: collapsed ? 0.5 : 1, opacity: collapsed ? 0.5 : 1,
color: `rgb(${r}, ${g}, ${b})`, color: colorFromString(this.props.theme, action.type),
cursor: (index > 0) ? 'pointer' : 'default' cursor: (index > 0) ? 'pointer' : 'default'
}; };
return ( return (
<div style={{textDecoration: collapsed ? 'line-through' : 'none'}}> <div style={{textDecoration: collapsed ? 'line-through' : 'none'}}>
<LogMonitorEntryAction collapsed={collapsed} action={action} onClick={::this.handleActionClick} style={{...styles.entry, ...styleEntry}}/> <LogMonitorEntryAction
theme={this.props.theme}
collapsed={collapsed}
action={action}
onClick={::this.handleActionClick}
style={{...styles.entry, ...styleEntry}}/>
{!collapsed && {!collapsed &&
<div style={{ <div style={{
borderBottom: '1px solid #20262c', borderBottom: '1px solid #20262c',

View File

@ -3,9 +3,10 @@ import JSONTree from './JSONTree';
const styles = { const styles = {
wrapper: { wrapper: {
backgroundColor: '#343c45', borderTopWidth: 1,
borderTop: '1px solid #3f464d', borderBottomWidth: 1,
borderBottom: '1px solid #3f464d' borderTopStyle: 'solid',
borderBottomStyle: 'solid',
}, },
actionBar: { actionBar: {
paddingTop: 4, paddingTop: 4,
@ -22,14 +23,20 @@ export default class LogMonitorAction extends React.Component {
renderPayload(payload) { renderPayload(payload) {
return ( return (
<div style={styles.payload}> <div style={styles.payload}>
{ Object.keys(payload).length > 0 ? <JSONTree keyName={'payload'} data={payload}/> : "" } { Object.keys(payload).length > 0 ? <JSONTree theme={this.props.theme} keyName={'payload'} data={payload}/> : "" }
</div> </div>
); );
} }
render() { render() {
const { type, ...payload } = this.props.action; const { type, ...payload } = this.props.action;
return ( return (
<div style={{...styles.wrapper, ...this.props.style}} onClick={this.props.onClick}> <div style={{
...styles.wrapper,
backgroundColor: this.props.theme.base01,
borderTopColor: this.props.theme.base00,
borderBottomColor: this.props.theme.base02,
...this.props.style
}} onClick={this.props.onClick}>
<div style={styles.actionBar}>{type}</div> <div style={styles.actionBar}>{type}</div>
{!this.props.collapsed ? '' : ''} {!this.props.collapsed ? '' : ''}
</div> </div>

5
src/react/theme.js Normal file
View File

@ -0,0 +1,5 @@
export default {
ocean: {
}
};

View File

@ -0,0 +1,5 @@
import ocean from "./ocean";
export default {
ocean,
};

20
src/react/themes/ocean.js Normal file
View File

@ -0,0 +1,20 @@
export default {
scheme: 'Ocean',
author: 'Chris Kempson (http://chriskempson.com)',
base00: '#2b303b',
base01: '#343d46',
base02: '#4f5b66',
base03: '#65737e',
base04: '#a7adba',
base05: '#c0c5ce',
base06: '#dfe1e8',
base07: '#eff1f5',
base08: '#bf616a',
base09: '#d08770',
base0A: '#ebcb8b',
base0B: '#a3be8c',
base0C: '#96b5b4',
base0D: '#8fa1b3',
base0E: '#b48ead',
base0F: '#ab7967'
}