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

semi working input state store

This commit is contained in:
sveisvei 2016-10-25 10:42:38 +02:00
parent d12396cf57
commit ab64d7eb1c
11 changed files with 274 additions and 238 deletions

View File

@ -1,78 +1,88 @@
import React, { PropTypes } from 'react';
import React, { Component, PropTypes } from 'react';
import { Input, Switch, Button } from 'react-toolbox';
import AddFeatureToggleStrategy from './strategies-for-toggle';
import SelectStrategies from './strategies-for-toggle';
import SelectedStrategies from './selected-strategies';
const AddFeatureToggleComponent = ({
strategies,
featureToggle,
updateField,
addStrategy,
removeStrategy,
onSubmit,
onCancel,
editmode,
}) => {
const {
name, // eslint-disable-line
description,
enabled,
} = featureToggle;
const configuredStrategies = featureToggle.strategies;
class AddFeatureToggleComponent extends Component {
return (
<form onSubmit={onSubmit}>
<section>
<Input
type="text"
label="Name"
name="name"
disabled={editmode}
required
value={name}
onChange={updateField.bind(this, 'name')} />
<Input
type="text"
multiline label="Description"
required
value={description}
onChange={updateField.bind(this, 'description')} />
componentWillMount () {
// TODO unwind this stuff
if (this.props.initCallRequired === true) {
this.props.init(this.props.input);
}
}
render () {
const {
input,
setValue,
addStrategy,
removeStrategy,
onSubmit,
onCancel,
editmode = false,
} = this.props;
const {
name, // eslint-disable-line
description,
enabled,
} = input;
const configuredStrategies = input.strategies || [];
return (
<form onSubmit={onSubmit(input)}>
<section>
<Input
type="text"
label="Name"
name="name"
disabled={editmode}
required
value={name}
onChange={(v) => setValue('name', v)} />
<Input
type="text"
multiline label="Description"
required
value={description}
onChange={(v) => setValue('description', v)} />
<br />
<Switch
checked={enabled}
label="Enabled"
onChange={(v) => setValue('enabled', v)} />
<br />
</section>
<section>
<strong>Activation strategies</strong>
<SelectedStrategies
configuredStrategies={configuredStrategies}
removeStrategy={removeStrategy} />
</section>
<section>
<SelectStrategies addStrategy={addStrategy} />
</section>
<br />
<Switch
checked={enabled}
label="Enabled"
onChange={updateField.bind(this, 'enabled')} />
<Button type="submit" raised primary label={editmode ? 'Update' : 'Create'} />
&nbsp;
<Button type="cancel" raised label="Cancel" onClick={onCancel} />
</form>
);
}
<br />
</section>
<section>
<strong>Activation strategies</strong>
<SelectedStrategies
configuredStrategies={configuredStrategies}
removeStrategy={removeStrategy} />
</section>
<section>
<AddFeatureToggleStrategy strategies={strategies} addStrategy={addStrategy} />
</section>
<br />
<Button type="submit" raised primary label={editmode ? 'Update' : 'Create'} />
&nbsp;
<Button type="cancel" raised label="Cancel" onClick={onCancel} />
</form>
);
};
AddFeatureToggleComponent.propTypes = {
strategies: PropTypes.array.required,
featureToggle: PropTypes.object,
updateField: PropTypes.func.required,
input: PropTypes.object,
setValue: PropTypes.func.required,
addStrategy: PropTypes.func.required,
removeStrategy: PropTypes.func.required,
onSubmit: PropTypes.func.required,

View File

@ -1,80 +1,36 @@
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { createFeatureToggles } from '../../store/feature-actions';
import { fetchStrategies } from '../../store/strategy-actions';
import AddFeatureToggleUI from './add-component';
import AddComponent from './add-component';
import { createMapper, createActions } from '../input-helpers';
class AddFeatureToggle extends React.Component {
constructor () {
super();
this.state = {
name: '',
description: '',
enabled: false,
strategies: [],
};
}
const ID = 'add-feature-toggle';
const mapStateToProps = createMapper({ id: ID });
const prepare = (methods, dispatch) => {
methods.onSubmit = (input) => (
(e) => {
e.preventDefault();
// TODO: should add error handling
createFeatureToggles(input)(dispatch)
.then(() => methods.clear())
.then(() => window.history.back());
}
);
static propTypes () {
return {
dispatch: PropTypes.func.isRequired,
strategies: PropTypes.array,
};
}
static contextTypes = {
router: React.PropTypes.object,
}
onSubmit = (evt) => {
evt.preventDefault();
this.props.dispatch(createFeatureToggles(this.state));
this.context.router.push('/features');
methods.onCancel = () => {
window.history.back();
};
onCancel = (evt) => {
evt.preventDefault();
this.context.router.push('/features');
methods.addStrategy = (v) => {
methods.pushToList('strategies', v);
};
updateField = (key, value) => {
const change = {};
change[key] = value;
this.setState(change);
methods.removeStrategy = (v) => {
methods.removeFromList('strategies', v);
};
addStrategy = (strategy) => {
const strategies = this.state.strategies;
strategies.push(strategy);
this.setState({ strategies });
}
return methods;
};
const actions = createActions({ id: ID, prepare });
removeStrategy = (strategy) => {
const strategies = this.state.strategies.filter(s => s !== strategy);
this.setState({ strategies });
}
componentDidMount () {
this.props.fetchStrategies();
}
render () {
return (
<AddFeatureToggleUI
strategies={this.props.strategies}
featureToggle={this.state}
updateField={this.updateField}
addStrategy={this.addStrategy}
removeStrategy={this.removeStrategy}
onSubmit={this.onSubmit}
onCancel={this.onCancel}
/>
);
}
}
const mapStateToProps = (state) => ({
strategies: state.strategies.get('list').toArray(),
});
export default connect(mapStateToProps, { fetchStrategies })(AddFeatureToggle);
export default connect(mapStateToProps, actions)(AddComponent);

View File

@ -1,86 +1,62 @@
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { editFeatureToggle } from '../../store/feature-actions';
import AddFeatureToggleComponent from './add-component';
import { fetchStrategies } from '../../store/strategy-actions';
import { requestUpdateFeatureToggle } from '../../store/feature-actions';
import AddComponent from './add-component';
import { createMapper, createActions } from '../input-helpers';
const mapStateToProps = (state, ownProps) => ({
strategies: state.strategies.get('list').toArray(),
featureToggle: state.features.toJS().find(toggle => toggle.name === ownProps.featureToggleName) || {},
const ID = 'edit-feature-toggle';
function getId (props) {
return [ID, props.featureToggleName];
}
// TODO: need to scope to the active featureToggle
// best is to emulate the "input-storage"?
const mapStateToProps = createMapper({
id: getId,
getDefault: (state, ownProps) => {
if (ownProps.featureToggleName) {
const match = state.features.findEntry((entry) => entry.get('name') === ownProps.featureToggleName);
if (match && match[1]) {
return match[1].toJS();
}
}
return {};
},
prepare: (props) => {
props.editmode = true;
return props;
},
});
class EditFeatureToggle extends React.Component {
constructor (props) {
super(props);
this.state = {
name: props.featureToggle.name || '',
description: props.featureToggle.description || '',
enabled: props.featureToggle.enabled || false,
strategies: props.featureToggle.strategies || [],
};
}
const prepare = (methods, dispatch) => {
methods.onSubmit = (input) => (
(e) => {
e.preventDefault();
// TODO: should add error handling
requestUpdateFeatureToggle(input)(dispatch)
.then(() => methods.clear())
.then(() => window.history.back());
}
);
static propTypes () {
return {
dispatch: PropTypes.func.isRequired,
strategies: PropTypes.array,
featureToggle: PropTypes.featureToggle.isRequired,
fetchFeatureToggles: PropTypes.func.isRequired,
};
}
componentDidMount () {
// todo fetch feature if missing? (reload of page does not fetch data from url)
this.props.fetchStrategies();
}
static contextTypes = {
router: React.PropTypes.object,
}
onSubmit = (evt) => {
evt.preventDefault();
this.props.dispatch(editFeatureToggle(this.state));
this.context.router.push('/features');
methods.onCancel = () => {
window.history.back();
};
onCancel = (evt) => {
evt.preventDefault();
this.context.router.push('/features');
methods.addStrategy = (v) => {
methods.pushToList('strategies', v);
};
updateField = (key, value) => {
const change = {};
change[key] = value;
this.setState(change);
methods.removeStrategy = (v) => {
methods.removeFromList('strategies', v);
};
addStrategy = (strategy) => {
const strategies = this.state.strategies;
strategies.push(strategy);
this.setState({ strategies });
}
return methods;
};
removeStrategy = (strategy) => {
const strategies = this.state.strategies.filter(s => s !== strategy);
this.setState({ strategies });
}
const actions = createActions({
id: getId,
prepare,
});
render () {
return (
<AddFeatureToggleComponent
editmode="true"
strategies={this.props.strategies}
featureToggle={this.state}
updateField={this.updateField}
addStrategy={this.addStrategy}
removeStrategy={this.removeStrategy}
onSubmit={this.onSubmit}
onCancel={this.onCancel}
/>
);
}
}
export default connect(mapStateToProps, { fetchStrategies })(EditFeatureToggle);
export default connect(mapStateToProps, actions)(AddComponent);

View File

@ -0,0 +1,8 @@
import { connect } from 'react-redux';
import SelectStrategies from './select-strategies';
import { fetchStrategies } from '../../store/strategy-actions';
export default connect((state) => ({
strategies: state.strategies.get('list').toArray(),
}), { fetchStrategies })(SelectStrategies);

View File

@ -18,6 +18,17 @@ class SelectStrategies extends React.Component {
};
}
componentWillMount () {
this.props.fetchStrategies();
}
componentWillReceiveProps (nextProps) {
// this will fix async strategies list loading after mounted
if (!this.state.selectedStrategy && nextProps.strategies.length > 0) {
this.setState({ selectedStrategy: nextProps.strategies[0] });
}
}
handleChange = (evt) => {
const strategyName = evt.target.value;
const selectedStrategy = this.props.strategies.find(s => s.name === strategyName);
@ -63,7 +74,11 @@ class SelectStrategies extends React.Component {
padding: '10px',
};
const selectedStrategy = this.state.selectedStrategy;
const selectedStrategy = this.state.selectedStrategy || this.props.strategies[0];
if (!selectedStrategy) {
return <div>Strategies loading...</div>;
}
return (
<div style={style}>

View File

@ -19,7 +19,7 @@ class SelectedStrategies extends React.Component {
render () {
const removeStrategy = this.props.removeStrategy;
const strategies = this.props.configuredStrategies.map((s, index) => (
const configuredStrategies = this.props.configuredStrategies.map((s, index) => (
<Chip
key={`${index}-${s.name}`}
deletable
@ -31,7 +31,7 @@ class SelectedStrategies extends React.Component {
));
return (
<div>
{strategies.length > 0 ? strategies : <p>No activation strategies added</p>}
{configuredStrategies.length > 0 ? configuredStrategies : <p>No activation strategies added</p>}
</div>
);
}

View File

@ -1,5 +1,5 @@
import React, { PropTypes } from 'react';
import SelectStrategies from './select-strategies';
import SelectStrategies from './select-strategies-container';
class AddStrategiesToToggle extends React.Component {
constructor () {
@ -11,7 +11,6 @@ class AddStrategiesToToggle extends React.Component {
static propTypes () {
return {
strategies: PropTypes.array.isRequired,
addStrategy: PropTypes.func.isRequired,
};
}
@ -39,12 +38,11 @@ class AddStrategiesToToggle extends React.Component {
}
render () {
return this.state.showConfigure ?
<SelectStrategies
strategies={this.props.strategies}
cancelConfig={this.cancelConfig}
addStrategy={this.addStrategy} /> :
this.renderAddLink();
return (
this.state.showConfigure ?
<SelectStrategies cancelConfig={this.cancelConfig} addStrategy={this.addStrategy} /> :
this.renderAddLink()
);
}
}

View File

@ -1,32 +1,63 @@
import { createInc, createClear, createSet } from '../store/input-actions';
import {
createInc,
createClear,
createSet,
createPop,
createPush,
createInit,
} from '../store/input-actions';
export function createMapper (id, prepare = (v) => v) {
return (state) => {
function getId (id, ownProps) {
if (typeof id === 'function') {
return id(ownProps); // should return array...
}
return [id];
}
export function createMapper ({ id, getDefault, prepare = (v) => v }) {
return (state, ownProps) => {
let input;
if (state.input.has(id)) {
input = state.input.get(id).toJS();
let initCallRequired = false;
const scope = getId(id, ownProps);
if (state.input.hasIn(scope)) {
input = state.input.getIn(scope).toJS();
} else {
input = {};
initCallRequired = true;
input = getDefault ? getDefault(state, ownProps) : {};
}
return prepare({
initCallRequired,
input,
}, state);
}, state, ownProps);
};
}
export function createActions (id, prepare = (v) => v) {
return (dispatch) => (prepare({
export function createActions ({ id, prepare = (v) => v }) {
return (dispatch, ownProps) => (prepare({
clear () {
dispatch(createClear({ id }));
dispatch(createClear({ id: getId(id, ownProps) }));
},
init (value) {
dispatch(createInit({ id: getId(id, ownProps), value }));
},
setValue (key, value) {
dispatch(createSet({ id, key, value }));
dispatch(createSet({ id: getId(id, ownProps), key, value }));
},
pushToList (key, value) {
dispatch(createPush({ id: getId(id, ownProps), key, value }));
},
removeFromList (key, value) {
dispatch(createPop({ id: getId(id, ownProps), key, value }));
},
incValue (key) {
dispatch(createInc({ id, key }));
dispatch(createInc({ id: getId(id, ownProps), key }));
},
}, dispatch));
}, dispatch, ownProps));
}

View File

@ -7,7 +7,7 @@ import AddStrategy, { PARAM_PREFIX } from './add-strategy';
const ID = 'add-strategy';
const actions = createActions(ID, (methods, dispatch) => {
const prepare = (methods, dispatch) => {
methods.onSubmit = (input) => (
(e) => {
e.preventDefault();
@ -35,6 +35,11 @@ const actions = createActions(ID, (methods, dispatch) => {
return methods;
};
const actions = createActions({
id: ID,
prepare,
});
export default connect(createMapper(ID), actions)(AddStrategy);
export default connect(createMapper({ id: ID }), actions)(AddStrategy);

View File

@ -1,11 +1,17 @@
export const actions = {
SET_VALUE: 'SET_VALUE',
INCREMENT_VALUE: 'INCREMENT_VALUE',
LIST_PUSH: 'LIST_PUSH',
LIST_POP: 'LIST_POP',
CLEAR: 'CLEAR',
INIT: 'INIT',
};
export const createInit = ({ id, value }) => ({ type: actions.INIT, id, value });
export const createInc = ({ id, key }) => ({ type: actions.INCREMENT_VALUE, id, key });
export const createSet = ({ id, key, value }) => ({ type: actions.SET_VALUE, id, key, value });
export const createPush = ({ id, key, value }) => ({ type: actions.LIST_PUSH, id, key, value });
export const createPop = ({ id, key, value }) => ({ type: actions.LIST_POP, id, key, value });
export const createClear = ({ id }) => ({ type: actions.CLEAR, id });
export default actions;

View File

@ -1,41 +1,68 @@
import { Map as $Map } from 'immutable';
import { Map as $Map, List, fromJS } from 'immutable';
import actions from './input-actions';
function getInitState () {
return new $Map();
}
function init (state, { id, value }) {
state = assertId(state, id);
return state.setIn(id, fromJS(value));
}
function assertId (state, id) {
if (!state.has(id)) {
return state.set(id, new $Map({ inputId: id }));
if (!state.hasIn(id)) {
return state.setIn(id, new $Map({ inputId: id }));
}
return state;
}
function assertList (state, id, key) {
if (!state.getIn(id).has(key)) {
return state.setIn(id.concat([key]), new List());
}
return state;
}
function setKeyValue (state, { id, key, value }) {
state = assertId(state, id);
return state.setIn([id, key], value);
return state.setIn(id.concat([key]), value);
}
function increment (state, { id, key }) {
state = assertId(state, id);
return state.updateIn([id, key], (value = 0) => value + 1);
return state.updateIn(id.concat([key]), (value = 0) => value + 1);
}
function clear (state, { id }) {
if (state.has(id)) {
return state.remove(id);
if (state.hasIn(id)) {
return state.removeIn(id);
}
return state;
}
const inputState = (state = getInitState(), action) => {
function addToList (state, { id, key, value }) {
state = assertId(state, id);
state = assertList(state, id, key);
return state.updateIn(id.concat([key]), (list) => list.push(value));
}
function removeFromList (state, { id, key, value }) {
state = assertId(state, id);
state = assertList(state, id, key);
return state.updateIn(id.concat([key]), (list) => list.remove(list.indexOf(value)));
}
const inputState = (state = getInitState(), action) => {
if (!action.id) {
return state;
}
switch (action.type) {
case actions.INIT:
return init(state, action);
case actions.SET_VALUE:
if (actions.key != null && actions.value != null) {
throw new Error('Missing required key / value');
@ -43,6 +70,10 @@ const inputState = (state = getInitState(), action) => {
return setKeyValue(state, action);
case actions.INCREMENT_VALUE:
return increment(state, action);
case actions.LIST_PUSH:
return addToList(state, action);
case actions.LIST_POP:
return removeFromList(state, action);
case actions.CLEAR:
return clear(state, action);
default: