diff --git a/frontend/src/component/common/index.js b/frontend/src/component/common/index.js index a495e04f0d..0e6e4ad458 100644 --- a/frontend/src/component/common/index.js +++ b/frontend/src/component/common/index.js @@ -29,9 +29,7 @@ export const HeaderTitle = ({ title, actions, subtitle }) => ( {subtitle && {subtitle}} -
- {actions} -
+ {actions &&
{actions}
} ); diff --git a/frontend/src/component/feature/form/strategies-add.jsx b/frontend/src/component/feature/form/strategies-add.jsx index 0b2dc3c8c1..1f46b5d8fa 100644 --- a/frontend/src/component/feature/form/strategies-add.jsx +++ b/frontend/src/component/feature/form/strategies-add.jsx @@ -14,9 +14,8 @@ class AddStrategy extends React.Component { addStrategy = (strategyName) => { const selectedStrategy = this.props.strategies.find(s => s.name === strategyName); const parameters = {}; - const keys = Object.keys(selectedStrategy.parametersTemplate || {}); - keys.forEach(prop => { parameters[prop] = ''; }); + selectedStrategy.parameters.forEach(({ name }) => { parameters[name] = ''; }); this.props.addStrategy({ name: selectedStrategy.name, @@ -40,7 +39,9 @@ class AddStrategy extends React.Component { Add Strategy: - {this.props.strategies.map((s) => this.addStrategy(s.name)}>{s.name})} + {this.props.strategies.map((s) => + this.addStrategy(s.name)}>{s.name}) + } ); diff --git a/frontend/src/component/feature/form/strategies-list.jsx b/frontend/src/component/feature/form/strategies-list.jsx index d62e049dab..1c4b04a576 100644 --- a/frontend/src/component/feature/form/strategies-list.jsx +++ b/frontend/src/component/feature/form/strategies-list.jsx @@ -22,13 +22,13 @@ class StrategiesList extends React.Component { return No strategies added; } - const blocks = configuredStrategies.map((strat, i) => ( + const blocks = configuredStrategies.map((strategy, i) => ( s.name === strat.name)} /> + strategyDefinition={strategies.find(s => s.name === strategy.name)} /> )); return (
diff --git a/frontend/src/component/feature/form/strategy-configure.jsx b/frontend/src/component/feature/form/strategy-configure.jsx index 3e37c0e60d..5d2aabb283 100644 --- a/frontend/src/component/feature/form/strategy-configure.jsx +++ b/frontend/src/component/feature/form/strategy-configure.jsx @@ -47,24 +47,23 @@ class StrategyConfigure extends React.Component { this.props.removeStrategy(); } - renderInputFields (strategyDefinition) { - if (strategyDefinition.parametersTemplate) { - const keys = Object.keys(strategyDefinition.parametersTemplate); - if (keys.length === 0) { - return null; - } - return keys.map(field => { - const type = strategyDefinition.parametersTemplate[field]; - let value = this.props.strategy.parameters[field]; + renderInputFields ({ parameters }) { + if (parameters && parameters.length > 0) { + return parameters.map(({ name, type, description, required }) => { + let value = this.props.strategy.parameters[name]; if (type === 'percentage') { if (value == null || (typeof value === 'string' && value === '')) { value = 50; // default value } - return (); + return ( +
+ + {description &&

{description}

} +
+ ); } else if (type === 'list') { let list = []; if (typeof value === 'string') { @@ -73,33 +72,44 @@ class StrategyConfigure extends React.Component { .split(',') .filter(Boolean); } - return (); + return ( +
+ + {description &&

{description}

} +
+ ); } else if (type === 'number') { return ( - +
+ + {description &&

{description}

} +
); } else { return ( - +
+ + {description &&

{description}

} +
); } }); diff --git a/frontend/src/component/feature/form/strategy-input-list.jsx b/frontend/src/component/feature/form/strategy-input-list.jsx index 1a93ce5200..aa63e09361 100644 --- a/frontend/src/component/feature/form/strategy-input-list.jsx +++ b/frontend/src/component/feature/form/strategy-input-list.jsx @@ -1,36 +1,80 @@ -import React from 'react'; +import React, { Component, PropTypes } from 'react'; import { Textfield, IconButton, Chip, } from 'react-mdl'; -export default ({ field, list, setConfig }) => ( -
-
{field}
- {list.map((entryValue, index) => ( - { - list[index] = null; - setConfig(field, list.filter(Boolean).join(',')); - }}>{entryValue} - ))} +export default class InputList extends Component { -
{ + static propTypes = { + field: PropTypes.string.isRequired, + list: PropTypes.array.isRequired, + setConfig: PropTypes.func.isRequired, + } + + onBlur = (e) => { + this.setValue(e); + window.removeEventListener('keydown', this.onKeyHandler, false); + } + + onFocus = (e) => { + e.preventDefault(); + e.stopPropagation(); + window.addEventListener('keydown', this.onKeyHandler, false); + } + + onKeyHandler = (e) => { + if (e.key === 'Enter') { + this.setValue(); e.preventDefault(); e.stopPropagation(); + } + } + + setValue = (e) => { + if (e) { + e.preventDefault(); + e.stopPropagation(); + } + + const { field, list, setConfig } = this.props; + const inputValue = document.querySelector(`[name="${field}_input"]`); + if (inputValue && inputValue.value) { + list.push(inputValue.value); + inputValue.value = ''; + setConfig(field, list.join(',')); + } + } + + onClose (index) { + const { field, list, setConfig } = this.props; + list[index] = null; + setConfig(field, list.length === 1 ? '' : list.filter(Boolean).join(',')); + } + + render () { + const { name, list } = this.props; + return (
+

{name}

+ {list.map((entryValue, index) => ( + this.onClose(index)}>{entryValue} + ))} - const inputValue = document.querySelector(`[name="${field}_input"]`); - if (inputValue && inputValue.value) { - list.push(inputValue.value); - inputValue.value = ''; - setConfig(field, list.join(',')); - } - }}>
- - + +
- -
-); +
); + } +} diff --git a/frontend/src/component/feature/form/strategy-input-persentage.jsx b/frontend/src/component/feature/form/strategy-input-persentage.jsx index 7c718a9db8..563d949de1 100644 --- a/frontend/src/component/feature/form/strategy-input-persentage.jsx +++ b/frontend/src/component/feature/form/strategy-input-persentage.jsx @@ -8,9 +8,9 @@ const labelStyle = { fontSize: '12px', }; -export default ({ field, value, onChange }) => ( +export default ({ name, value, onChange }) => (
-
{field}: {value}%
- +
{name}: {value}%
+
); diff --git a/frontend/src/component/input-helpers.js b/frontend/src/component/input-helpers.js index 8afefcec4e..c0b24790bc 100644 --- a/frontend/src/component/input-helpers.js +++ b/frontend/src/component/input-helpers.js @@ -57,8 +57,8 @@ export function createActions ({ id, prepare = (v) => v }) { dispatch(createPop({ id: getId(id, ownProps), key, index })); }, - updateInList (key, index, newValue) { - dispatch(createUp({ id: getId(id, ownProps), key, index, newValue })); + updateInList (key, index, newValue, merge = false) { + dispatch(createUp({ id: getId(id, ownProps), key, index, newValue, merge })); }, incValue (key) { diff --git a/frontend/src/component/strategies/add-container.js b/frontend/src/component/strategies/add-container.js index aba64bbba8..b7e865413b 100644 --- a/frontend/src/component/strategies/add-container.js +++ b/frontend/src/component/strategies/add-container.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import { createMapper, createActions } from '../input-helpers'; import { createStrategy } from '../../store/strategy/actions'; -import AddStrategy, { PARAM_PREFIX, TYPE_PREFIX } from './add-strategy'; +import AddStrategy from './add-strategy'; const ID = 'add-strategy'; @@ -12,15 +12,28 @@ const prepare = (methods, dispatch) => { (e) => { e.preventDefault(); - const parametersTemplate = {}; - Object.keys(input).forEach(key => { - if (key.startsWith(PARAM_PREFIX)) { - parametersTemplate[input[key]] = input[key.replace(PARAM_PREFIX, TYPE_PREFIX)] || 'string'; - } - }); - input.parametersTemplate = parametersTemplate; - createStrategy(input)(dispatch) + + // clean + const parameters = input.parameters + .filter((name) => !!name) + .map(({ + name, + type = 'string', + description = '', + required = false, + }) => ({ + name, + type, + description, + required, + })); + + createStrategy({ + name: input.name, + description: input.description, + parameters, + })(dispatch) .then(() => methods.clear()) // somewhat quickfix / hacky to go back.. .then(() => window.history.back()); diff --git a/frontend/src/component/strategies/add-strategy.jsx b/frontend/src/component/strategies/add-strategy.jsx index d9ef8047f1..3bf98184ba 100644 --- a/frontend/src/component/strategies/add-strategy.jsx +++ b/frontend/src/component/strategies/add-strategy.jsx @@ -1,6 +1,6 @@ import React, { PropTypes } from 'react'; -import { Textfield, IconButton, Menu, MenuItem } from 'react-mdl'; +import { Textfield, IconButton, Menu, MenuItem, Checkbox } from 'react-mdl'; import { HeaderTitle, FormButtons } from '../common'; @@ -15,40 +15,60 @@ const trim = (value) => { function gerArrayWithEntries (num) { return Array.from(Array(num)); } -export const PARAM_PREFIX = 'param_'; -export const TYPE_PREFIX = 'type_'; -const genParams = (input, num = 0, setValue) => (
{gerArrayWithEntries(num).map((v, i) => { - const key = `${PARAM_PREFIX}${i + 1}`; - const typeKey = `${TYPE_PREFIX}${i + 1}`; - return ( -
- setValue(key, target.value)} - value={input[key]} /> -
- - {input[typeKey] || 'string'} - evt.preventDefault()} /> - - - setValue(typeKey, 'string')}>String - setValue(typeKey, 'percentage')}>Percentage - setValue(typeKey, 'list')}>List of values - setValue(typeKey, 'number')}>Number - -
+const Parameter = ({ set, input = {}, index }) => ( +
+ set({ name: target.value }, true)} + value={input.name} /> +
+ + {input.type || 'string'} + evt.preventDefault()} /> + + + set({ type: 'string' })}>String + set({ type: 'percentage' })}>Percentage + set({ type: 'list' })}>List of values + set({ type: 'number' })}>Number +
- ); -})}
); + set({ description: target.value })} + value={input.description} + /> + set({ required: !input.required })} + ripple + defaultChecked + /> +
+); + +const Parameters = ({ input = [], count = 0, updateInList }) => ( +
{ + gerArrayWithEntries(count) + .map((v, i) => updateInList('parameters', i, v, true)} + index={i} + input={input[i]} + />) +}
); const AddStrategy = ({ input, setValue, + updateInList, incValue, // clear, onCancel, @@ -78,7 +98,7 @@ const AddStrategy = ({
- {genParams(input, input._params, setValue)} + { e.preventDefault(); incValue('_params'); @@ -97,6 +117,7 @@ const AddStrategy = ({ AddStrategy.propTypes = { input: PropTypes.object, setValue: PropTypes.func, + updateInList: PropTypes.func, incValue: PropTypes.func, clear: PropTypes.func, onCancel: PropTypes.func, diff --git a/frontend/src/component/strategies/list-component.jsx b/frontend/src/component/strategies/list-component.jsx index 94befbb283..41aa438caf 100644 --- a/frontend/src/component/strategies/list-component.jsx +++ b/frontend/src/component/strategies/list-component.jsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { Link } from 'react-router'; -import { List, ListItem, ListItemContent, Chip, Icon, IconButton } from 'react-mdl'; +import { List, ListItem, ListItemContent, IconButton } from 'react-mdl'; import { HeaderTitle } from '../common'; class StrategiesListComponent extends Component { @@ -14,12 +14,6 @@ class StrategiesListComponent extends Component { this.props.fetchStrategies(); } - getParameterMap ({ parametersTemplate }) { - return Object.keys(parametersTemplate || {}).map(k => ( - {k} - )); - } - render () { const { strategies, removeStrategy } = this.props; diff --git a/frontend/src/component/strategies/show-strategy-component.js b/frontend/src/component/strategies/show-strategy-component.js index b2fbe45388..24fc36c56a 100644 --- a/frontend/src/component/strategies/show-strategy-component.js +++ b/frontend/src/component/strategies/show-strategy-component.js @@ -1,6 +1,5 @@ import React, { Component } from 'react'; -import { Grid, Cell } from 'react-mdl'; - +import { Grid, Cell, List, ListItem, ListItemContent } from 'react-mdl'; import { AppsLinkList, TogglesLinkList, HeaderTitle } from '../common'; class ShowStrategyComponent extends Component { @@ -16,13 +15,17 @@ class ShowStrategyComponent extends Component { } } - renderParameters (parametersTemplate) { - if (parametersTemplate) { - return Object.keys(parametersTemplate).map((name, i) => ( -
  • {name} ({parametersTemplate[name]})
  • + renderParameters (params) { + if (params) { + return params.map(({ name, type, description, required }, i) => ( + + + {name} ({type}) + + )); } else { - return
  • (no params)
  • ; + return (no params); } } @@ -41,28 +44,28 @@ class ShowStrategyComponent extends Component { const { name, description, - parametersTemplate = {}, + parameters = [], } = strategy; return (
    - +
    Parameters

    -
      - {this.renderParameters(parametersTemplate)} -
    + + {this.renderParameters(parameters)} +
    - +
    Applications using this strategy

    - +
    Toggles using this strategy

    diff --git a/frontend/src/store/input-actions.js b/frontend/src/store/input-actions.js index b664bb1b98..5b6f5bead6 100644 --- a/frontend/src/store/input-actions.js +++ b/frontend/src/store/input-actions.js @@ -13,7 +13,7 @@ export const createInc = ({ id, key }) => ({ type: actions.INCREMENT_VALUE, id, 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, index }) => ({ type: actions.LIST_POP, id, key, index }); -export const createUp = ({ id, key, index, newValue }) => ({ type: actions.LIST_UP, id, key, index, newValue }); +export const createUp = ({ id, key, index, newValue, merge }) => ({ type: actions.LIST_UP, id, key, index, newValue, merge }); export const createClear = ({ id }) => ({ type: actions.CLEAR, id }); export default actions; diff --git a/frontend/src/store/input-store.js b/frontend/src/store/input-store.js index 70de30ccf4..743c7c570f 100644 --- a/frontend/src/store/input-store.js +++ b/frontend/src/store/input-store.js @@ -48,11 +48,18 @@ function addToList (state, { id, key, value }) { return state.updateIn(id.concat([key]), (list) => list.push(value)); } -function updateInList (state, { id, key, index, newValue }) { +function updateInList (state, { id, key, index, newValue, merge }) { state = assertId(state, id); state = assertList(state, id, key); - return state.updateIn(id.concat([key]), (list) => list.set(index, newValue)); + return state.updateIn(id.concat([key]), (list) => { + if (merge && list.has(index)) { + newValue = list.get(index).merge(new $Map(newValue)); + } else if (typeof newValue !== 'string' ) { + newValue = fromJS(newValue); + } + return list.set(index, newValue); + }); } function removeFromList (state, { id, key, index }) {