1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00

Move components to separate files.

This commit is contained in:
Jari Bakken 2014-10-30 18:25:38 +01:00 committed by Ivar Conradi Østhus
parent e1f1cfc4d3
commit e7ecae51b7
12 changed files with 354 additions and 339 deletions

View File

@ -30,7 +30,7 @@
"nonbsp" : true,
"nonew" : true,
"plusplus" : false,
"quotmark" : true,
"quotmark" : false,
"undef" : true,
"unused" : true,
"strict" : false,

View File

@ -20,7 +20,7 @@
"start": "NODE_ENV=production node server.js",
"build": "./node_modules/.bin/webpack",
"dev": "NODE_ENV=development supervisor --ignore ./node_modules/,./public/js server.js",
"test": "jshint server.js lib test && jsxhint public/js/*.jsx && mocha test test/* && npm run coverage",
"test": "jshint server.js lib test && jsxhint public/js/**/*.jsx && mocha test test/* && npm run coverage",
"tdd": "mocha --watch test test/*",
"test-bamboo-ci": "mocha test test/*",
"coverage": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec",

View File

@ -0,0 +1,7 @@
var React = require('react');
var Unleash = require('./components/Unleash');
React.render(
<Unleash pollInterval={5000} />,
document.getElementById('content')
);

View File

@ -0,0 +1,21 @@
var React = require('react');
var ErrorMessages = React.createClass({
render: function() {
if (!this.props.errors.length) {
return <div/>;
}
var errorNodes = this.props.errors.map(function(e) {
return (<li key={e}>{e}</li>);
});
return (
<div className="alert alert-danger" role="alert">
<ul>{errorNodes}</ul>
</div>
);
}
});
module.exports = ErrorMessages;

View File

@ -0,0 +1,52 @@
var React = require('react');
var SavedFeature = require('./SavedFeature');
var UnsavedFeature = require('./UnsavedFeature');
var FeatureList = React.createClass({
render: function() {
var featureNodes = [];
this.props.unsavedFeatures.forEach(function(feature, idx) {
var key = 'new-' + idx;
featureNodes.push(
<UnsavedFeature
key={key}
feature={feature}
onSubmit={this.props.onFeatureSubmit}
onCancel={this.props.onFeatureCancel} />
);
}.bind(this));
this.props.savedFeatures.forEach(function(feature) {
featureNodes.push(
<SavedFeature
key={feature.name}
feature={feature}
onChange={this.props.onFeatureChanged} />
);
}.bind(this));
return (
<div className="container">
<div className='panel panel-primary'>
<div className='panel-heading'>
<h3 className='panel-title'>Features</h3>
<div className='text-right'>
<button type="button"
className="btn btn-default btn-sm"
onClick={this.props.onNewFeature}>
<span className="glyphicon glyphicon-plus"></span> New
</button>
</div>
</div>
<div className='panel-body'>
{featureNodes}
</div>
</div>
</div>
);
}
});
module.exports = FeatureList;

View File

@ -0,0 +1,14 @@
var React = require('react');
var Menu = React.createClass({
render: function() { return (
<nav className='navbar navbar-default' role='navigation'>
<div className='container'>
<a className='navbar-brand' href='#'>unleash admin</a>
</div>
</nav>
);
}
});
module.exports = Menu;

View File

@ -0,0 +1,32 @@
var React = require('react');
var SavedFeature = React.createClass({
onChange: function(event) {
this.props.onChange({
name: this.props.feature.name,
field: 'enabled',
value: event.target.checked
});
},
render: function() {
return (
<div className='row'>
<div className='col-md-1 text-right'>
<input
type='checkbox'
checked={this.props.feature.enabled}
onChange={this.onChange} />
</div>
<div
className='col-md-4'
title='{this.props.feature.description}'>{this.props.feature.name}</div>
<div className='col-md-2 col-md-offset-5'>
{this.props.feature.strategy}
</div>
</div>
);
}
});
module.exports = SavedFeature;

View File

@ -0,0 +1,131 @@
var React = require('react');
var reqwest = require('reqwest');
var Timer = require('../utils/Timer');
var Menu = require('./Menu');
var ErrorMessages = require('./ErrorMessages');
var FeatureList = require('./FeatureList');
var Unleash = React.createClass({
getInitialState: function() {
return {
savedFeatures: [],
unsavedFeatures: [],
errors: [],
timer: null
};
},
componentDidMount: function () {
this.loadFeaturesFromServer();
this.state.timer = new Timer(this.loadFeaturesFromServer, this.props.pollInterval);
this.state.timer.start();
},
componentWillUnmount: function () {
if (this.state.timer != null) {
this.state.timer.stop();
}
},
loadFeaturesFromServer: function () {
reqwest('features').then(this.setFeatures, this.handleError);
},
setFeatures: function (data) {
this.setState({savedFeatures: data.features});
},
handleError: function (error) {
this.state.errors.push(error);
},
updateFeature: function (changeRequest) {
var newFeatures = this.state.savedFeatures;
newFeatures.forEach(function(f){
if(f.name === changeRequest.name) {
f[changeRequest.field] = changeRequest.value;
}
});
this.setState({features: newFeatures});
this.state.timer.stop();
reqwest({
url: 'features/' + changeRequest.name,
method: 'patch',
type: 'json',
contentType: 'application/json',
data: JSON.stringify(changeRequest)
}).then(function() {
// all good
this.state.timer.start();
}.bind(this), this.handleError);
},
createFeature: function (feature) {
var unsaved = [], state = this.state;
this.state.unsavedFeatures.forEach(function(f) {
// TODO: make sure we don't overwrite an existing feature
if (f.name === feature.name) {
state.savedFeatures.unshift(f);
} else {
unsaved.push(f);
}
});
this.setState({unsavedFeatures: unsaved});
reqwest({
url: 'features',
method: 'post',
type: 'json',
contentType: 'application/json',
data: JSON.stringify(feature)
}).then(function(r) {
console.log(r.statusText);
}.bind(this), this.handleError);
},
newFeature: function() {
var blankFeature = {
name: '',
enabled: false,
strategy: 'default',
parameters: {}
};
this.state.unsavedFeatures.push(blankFeature);
this.forceUpdate();
},
cancelNewFeature: function (feature) {
var unsaved = [];
this.state.unsavedFeatures.forEach(function (f) {
if (f.name !== feature.name) {
unsaved.push(f);
}
});
this.setState({unsavedFeatures: unsaved});
},
render: function() {
return (
<div>
<Menu />
<ErrorMessages errors={this.state.errors} />
<FeatureList
unsavedFeatures={this.state.unsavedFeatures}
savedFeatures={this.state.savedFeatures}
onFeatureChanged={this.updateFeature}
onFeatureSubmit={this.createFeature}
onFeatureCancel={this.cancelNewFeature}
onNewFeature={this.newFeature} />
</div>
);
}
});
module.exports = Unleash;

View File

@ -0,0 +1,68 @@
var React = require('react');
var UnsavedFeature = React.createClass({
render: function() {
return (
<div className="bg-info new-feature-form row">
<form className="form-inline" role="form" ref="form">
<div className="checkbox col-md-1 text-right">
<input ref="enabled" type="checkbox" defaultValue={this.props.feature.enabled} />
</div>
<div className="form-group col-md-4">
<input
type="text"
className="form-control"
id="name"
ref="name"
defaultValue={this.props.feature.name}
placeholder="Enter name" />
<input className="form-control"
type="text"
ref="description"
placeholder="Enter description" />
</div>
<div className="form-group col-md-1 col-md-offset-5">
<select id="strategy"
ref="strategy"
className=""
defaultValue={this.props.feature.strategy}>
<option value="default">default</option>
</select>
</div>
<div className="form-group col-md-1">
<button className="btn btn-primary btn-xs" onClick={this.saveFeature}>
Save
</button>
<button className="btn btn-xs" onClick={this.cancelFeature}>
Cancel
</button>
</div>
</form>
</div>
);
},
saveFeature: function(e) {
e.preventDefault();
this.props.feature.name = this.refs.name.getDOMNode().value;
this.props.feature.description = this.refs.description.getDOMNode().value;
this.props.feature.strategy = this.refs.strategy.getDOMNode().value;
this.props.feature.enabled = this.refs.enabled.getDOMNode().checked;
this.props.onSubmit(this.props.feature);
},
cancelFeature: function(e) {
e.preventDefault();
this.props.onCancel(this.props.feature);
}
});
module.exports = UnsavedFeature;

View File

@ -1,336 +0,0 @@
/* jshint quotmark:false */
var React = require('react');
var reqwest = require('reqwest');
// Unleash
// - Menu
// - FeatureList
// - UnsavedFeature
// - SavedFeature
//
var Timer = function(cb, interval) {
this.cb = cb;
this.interval = interval;
this.timerId = null;
};
Timer.prototype.start = function() {
if (this.timerId != null) {
console.warn("timer already started");
}
console.log('starting timer');
this.timerId = setInterval(this.cb, this.interval);
};
Timer.prototype.stop = function() {
if (this.timerId == null) {
console.warn('no timer running');
} else {
console.log('stopping timer');
clearInterval(this.timerId);
this.timerId = null;
}
};
var Menu = React.createClass({
render: function() { return (
<nav className='navbar navbar-default' role='navigation'>
<div className='container'>
<a className='navbar-brand' href='#'>unleash admin</a>
</div>
</nav>
);
}
});
var UnsavedFeature = React.createClass({
render: function() {
return (
<div className="bg-info new-feature-form row">
<form className="form-inline" role="form" ref="form">
<div className="checkbox col-md-1 text-right">
<input ref="enabled" type="checkbox" defaultValue={this.props.feature.enabled} />
</div>
<div className="form-group col-md-4">
<input
type="text"
className="form-control"
id="name"
ref="name"
defaultValue={this.props.feature.name}
placeholder="Enter name" />
<input className="form-control"
type="text"
ref="description"
placeholder="Enter description" />
</div>
<div className="form-group col-md-1 col-md-offset-5">
<select id="strategy"
ref="strategy"
className=""
defaultValue={this.props.feature.strategy}>
<option value="default">default</option>
</select>
</div>
<div className="form-group col-md-1">
<button className="btn btn-primary btn-xs" onClick={this.saveFeature}>
Save
</button>
<button className="btn btn-xs" onClick={this.cancelFeature}>
Cancel
</button>
</div>
</form>
</div>
);
},
saveFeature: function(e) {
e.preventDefault();
this.props.feature.name = this.refs.name.getDOMNode().value;
this.props.feature.description = this.refs.description.getDOMNode().value;
this.props.feature.strategy = this.refs.strategy.getDOMNode().value;
this.props.feature.enabled = this.refs.enabled.getDOMNode().checked;
this.props.onSubmit(this.props.feature);
},
cancelFeature: function(e) {
e.preventDefault();
this.props.onCancel(this.props.feature);
}
});
var SavedFeature = React.createClass({
onChange: function(event) {
this.props.onChange({
name: this.props.feature.name,
field: 'enabled',
value: event.target.checked
});
},
render: function() {
return (
<div className='row'>
<div className='col-md-1 text-right'>
<input
type='checkbox'
checked={this.props.feature.enabled}
onChange={this.onChange} />
</div>
<div
className='col-md-4'
title='{this.props.feature.description}'>{this.props.feature.name}</div>
<div className='col-md-2 col-md-offset-5'>
{this.props.feature.strategy}
</div>
</div>
);
}
});
var FeatureList = React.createClass({
render: function() {
var featureNodes = [];
this.props.unsavedFeatures.forEach(function(feature, idx) {
var key = 'new-' + idx;
featureNodes.push(
<UnsavedFeature
key={key}
feature={feature}
onSubmit={this.props.onFeatureSubmit}
onCancel={this.props.onFeatureCancel} />
);
}.bind(this));
this.props.savedFeatures.forEach(function(feature) {
featureNodes.push(
<SavedFeature
key={feature.name}
feature={feature}
onChange={this.props.onFeatureChanged} />
);
}.bind(this));
return (
<div className="container">
<div className='panel panel-primary'>
<div className='panel-heading'>
<h3 className='panel-title'>Features</h3>
<div className='text-right'>
<button type="button"
className="btn btn-default btn-sm"
onClick={this.props.onNewFeature}>
<span className="glyphicon glyphicon-plus"></span> New
</button>
</div>
</div>
<div className='panel-body'>
{featureNodes}
</div>
</div>
</div>
);
}
});
var ErrorMessages = React.createClass({
render: function() {
if (!this.props.errors.length) {
return <div/>;
}
var errorNodes = this.props.errors.map(function(e) {
return (<li key={e}>{e}</li>);
});
return (
<div className="alert alert-danger" role="alert">
<ul>{errorNodes}</ul>
</div>
);
}
});
var Unleash = React.createClass({
getInitialState: function() {
return {
savedFeatures: [],
unsavedFeatures: [],
errors: [],
timer: null
};
},
componentDidMount: function () {
this.loadFeaturesFromServer();
this.state.timer = new Timer(this.loadFeaturesFromServer, this.props.pollInterval);
this.state.timer.start();
},
componentWillUnmount: function () {
if (this.state.timer != null) {
this.state.timer.stop();
}
},
loadFeaturesFromServer: function () {
reqwest('features').then(this.setFeatures, this.handleError);
},
setFeatures: function (data) {
this.setState({savedFeatures: data.features});
},
handleError: function (error) {
this.state.errors.push(error);
},
updateFeature: function (changeRequest) {
var newFeatures = this.state.savedFeatures;
newFeatures.forEach(function(f){
if(f.name === changeRequest.name) {
f[changeRequest.field] = changeRequest.value;
}
});
this.setState({features: newFeatures});
this.state.timer.stop();
reqwest({
url: 'features/' + changeRequest.name,
method: 'patch',
type: 'json',
contentType: 'application/json',
data: JSON.stringify(changeRequest)
}).then(function() {
// all good
this.state.timer.start();
}.bind(this), this.handleError);
},
createFeature: function (feature) {
var unsaved = [], state = this.state;
this.state.unsavedFeatures.forEach(function(f) {
// TODO: make sure we don't overwrite an existing feature
if (f.name === feature.name) {
state.savedFeatures.unshift(f);
} else {
unsaved.push(f);
}
});
this.setState({unsavedFeatures: unsaved});
reqwest({
url: 'features',
method: 'post',
type: 'json',
contentType: 'application/json',
data: JSON.stringify(feature)
}).then(function(r) {
console.log(r.statusText);
}.bind(this), this.handleError);
},
newFeature: function() {
var blankFeature = {
name: '',
enabled: false,
strategy: 'default',
parameters: {}
};
this.state.unsavedFeatures.push(blankFeature);
this.forceUpdate();
},
cancelNewFeature: function (feature) {
var unsaved = [];
this.state.unsavedFeatures.forEach(function (f) {
if (f.name !== feature.name) {
unsaved.push(f);
}
});
this.setState({unsavedFeatures: unsaved});
},
render: function() {
return (
<div>
<Menu />
<ErrorMessages errors={this.state.errors} />
<FeatureList
unsavedFeatures={this.state.unsavedFeatures}
savedFeatures={this.state.savedFeatures}
onFeatureChanged={this.updateFeature}
onFeatureSubmit={this.createFeature}
onFeatureCancel={this.cancelNewFeature}
onNewFeature={this.newFeature}
/>
</div>
);
}
});
React.render(
<Unleash pollInterval={5000} />,
document.getElementById('content')
);

View File

@ -0,0 +1,26 @@
var Timer = function(cb, interval) {
this.cb = cb;
this.interval = interval;
this.timerId = null;
};
Timer.prototype.start = function() {
if (this.timerId != null) {
console.warn("timer already started");
}
console.log('starting timer');
this.timerId = setInterval(this.cb, this.interval);
};
Timer.prototype.stop = function() {
if (this.timerId == null) {
console.warn('no timer running');
} else {
console.log('stopping timer');
clearInterval(this.timerId);
this.timerId = null;
}
};
module.exports = Timer;

View File

@ -3,7 +3,7 @@
module.exports = {
context: __dirname + '/public',
entry: './js/unleash',
entry: './js/app',
output: {
path: __dirname + '/public/js',