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

View File

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

View File

@ -1,86 +1,62 @@
import React, { PropTypes } from 'react';
import { connect } from 'react-redux'; 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) => ({ const ID = 'edit-feature-toggle';
strategies: state.strategies.get('list').toArray(), function getId (props) {
featureToggle: state.features.toJS().find(toggle => toggle.name === ownProps.featureToggleName) || {}, 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 { const prepare = (methods, dispatch) => {
constructor (props) { methods.onSubmit = (input) => (
super(props); (e) => {
this.state = { e.preventDefault();
name: props.featureToggle.name || '', // TODO: should add error handling
description: props.featureToggle.description || '', requestUpdateFeatureToggle(input)(dispatch)
enabled: props.featureToggle.enabled || false, .then(() => methods.clear())
strategies: props.featureToggle.strategies || [], .then(() => window.history.back());
}; }
} );
static propTypes () { methods.onCancel = () => {
return { window.history.back();
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');
}; };
onCancel = (evt) => { methods.addStrategy = (v) => {
evt.preventDefault(); methods.pushToList('strategies', v);
this.context.router.push('/features');
}; };
updateField = (key, value) => { methods.removeStrategy = (v) => {
const change = {}; methods.removeFromList('strategies', v);
change[key] = value;
this.setState(change);
}; };
addStrategy = (strategy) => { return methods;
const strategies = this.state.strategies; };
strategies.push(strategy);
this.setState({ strategies });
}
removeStrategy = (strategy) => { const actions = createActions({
const strategies = this.state.strategies.filter(s => s !== strategy); id: getId,
this.setState({ strategies }); prepare,
} });
render () { export default connect(mapStateToProps, actions)(AddComponent);
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);

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) => { handleChange = (evt) => {
const strategyName = evt.target.value; const strategyName = evt.target.value;
const selectedStrategy = this.props.strategies.find(s => s.name === strategyName); const selectedStrategy = this.props.strategies.find(s => s.name === strategyName);
@ -63,7 +74,11 @@ class SelectStrategies extends React.Component {
padding: '10px', padding: '10px',
}; };
const selectedStrategy = this.state.selectedStrategy; const selectedStrategy = this.state.selectedStrategy || this.props.strategies[0];
if (!selectedStrategy) {
return <div>Strategies loading...</div>;
}
return ( return (
<div style={style}> <div style={style}>

View File

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

View File

@ -1,5 +1,5 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import SelectStrategies from './select-strategies'; import SelectStrategies from './select-strategies-container';
class AddStrategiesToToggle extends React.Component { class AddStrategiesToToggle extends React.Component {
constructor () { constructor () {
@ -11,7 +11,6 @@ class AddStrategiesToToggle extends React.Component {
static propTypes () { static propTypes () {
return { return {
strategies: PropTypes.array.isRequired,
addStrategy: PropTypes.func.isRequired, addStrategy: PropTypes.func.isRequired,
}; };
} }
@ -39,12 +38,11 @@ class AddStrategiesToToggle extends React.Component {
} }
render () { render () {
return this.state.showConfigure ? return (
<SelectStrategies this.state.showConfigure ?
strategies={this.props.strategies} <SelectStrategies cancelConfig={this.cancelConfig} addStrategy={this.addStrategy} /> :
cancelConfig={this.cancelConfig} this.renderAddLink()
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) { function getId (id, ownProps) {
return (state) => { 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; let input;
if (state.input.has(id)) { let initCallRequired = false;
input = state.input.get(id).toJS(); const scope = getId(id, ownProps);
if (state.input.hasIn(scope)) {
input = state.input.getIn(scope).toJS();
} else { } else {
input = {}; initCallRequired = true;
input = getDefault ? getDefault(state, ownProps) : {};
} }
return prepare({ return prepare({
initCallRequired,
input, input,
}, state); }, state, ownProps);
}; };
} }
export function createActions (id, prepare = (v) => v) { export function createActions ({ id, prepare = (v) => v }) {
return (dispatch) => (prepare({ return (dispatch, ownProps) => (prepare({
clear () { clear () {
dispatch(createClear({ id })); dispatch(createClear({ id: getId(id, ownProps) }));
},
init (value) {
dispatch(createInit({ id: getId(id, ownProps), value }));
}, },
setValue (key, 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) { 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 ID = 'add-strategy';
const actions = createActions(ID, (methods, dispatch) => { const prepare = (methods, dispatch) => {
methods.onSubmit = (input) => ( methods.onSubmit = (input) => (
(e) => { (e) => {
e.preventDefault(); e.preventDefault();
@ -35,6 +35,11 @@ const actions = createActions(ID, (methods, dispatch) => {
return methods; 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 = { export const actions = {
SET_VALUE: 'SET_VALUE', SET_VALUE: 'SET_VALUE',
INCREMENT_VALUE: 'INCREMENT_VALUE', INCREMENT_VALUE: 'INCREMENT_VALUE',
LIST_PUSH: 'LIST_PUSH',
LIST_POP: 'LIST_POP',
CLEAR: 'CLEAR', 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 createInc = ({ id, key }) => ({ type: actions.INCREMENT_VALUE, id, key });
export const createSet = ({ id, key, value }) => ({ type: actions.SET_VALUE, id, key, value }); 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 const createClear = ({ id }) => ({ type: actions.CLEAR, id });
export default actions; 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'; import actions from './input-actions';
function getInitState () { function getInitState () {
return new $Map(); return new $Map();
} }
function init (state, { id, value }) {
state = assertId(state, id);
return state.setIn(id, fromJS(value));
}
function assertId (state, id) { function assertId (state, id) {
if (!state.has(id)) { if (!state.hasIn(id)) {
return state.set(id, new $Map({ inputId: 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; return state;
} }
function setKeyValue (state, { id, key, value }) { function setKeyValue (state, { id, key, value }) {
state = assertId(state, id); state = assertId(state, id);
return state.setIn([id, key], value); return state.setIn(id.concat([key]), value);
} }
function increment (state, { id, key }) { function increment (state, { id, key }) {
state = assertId(state, id); 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 }) { function clear (state, { id }) {
if (state.has(id)) { if (state.hasIn(id)) {
return state.remove(id); return state.removeIn(id);
} }
return state; 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) { if (!action.id) {
return state; return state;
} }
switch (action.type) { switch (action.type) {
case actions.INIT:
return init(state, action);
case actions.SET_VALUE: case actions.SET_VALUE:
if (actions.key != null && actions.value != null) { if (actions.key != null && actions.value != null) {
throw new Error('Missing required key / value'); throw new Error('Missing required key / value');
@ -43,6 +70,10 @@ const inputState = (state = getInitState(), action) => {
return setKeyValue(state, action); return setKeyValue(state, action);
case actions.INCREMENT_VALUE: case actions.INCREMENT_VALUE:
return increment(state, action); return increment(state, action);
case actions.LIST_PUSH:
return addToList(state, action);
case actions.LIST_POP:
return removeFromList(state, action);
case actions.CLEAR: case actions.CLEAR:
return clear(state, action); return clear(state, action);
default: default: