diff --git a/frontend/.babelrc b/frontend/.babelrc
index 4d3b7d3676..5e6d023488 100644
--- a/frontend/.babelrc
+++ b/frontend/.babelrc
@@ -1,5 +1,6 @@
{
"presets": ["react", "es2015", "stage-2"],
+ "plugins": ["transform-decorators-legacy"],
"env": {
"development": {
"presets": ["react-hmre"]
diff --git a/frontend/package.json b/frontend/package.json
index 74343f8386..fb008f2ff7 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -40,6 +40,8 @@
"normalize.css": "^5.0.0",
"react": "^15.3.1",
"react-addons-css-transition-group": "^15.3.1",
+ "react-dnd": "^2.1.4",
+ "react-dnd-html5-backend": "^2.1.2",
"react-dom": "^15.3.1",
"react-mdl": "^1.9.0",
"react-modal": "^1.6.4",
@@ -54,6 +56,7 @@
"babel-core": "^6.14.0",
"babel-eslint": "^7.1.0",
"babel-loader": "^6.2.5",
+ "babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-es2015": "^6.14.0",
"babel-preset-react": "^6.11.1",
"babel-preset-react-hmre": "^1.1.1",
diff --git a/frontend/src/component/feature/form-add-container.jsx b/frontend/src/component/feature/form-add-container.jsx
index 3afb20b3c2..4bf589dce1 100644
--- a/frontend/src/component/feature/form-add-container.jsx
+++ b/frontend/src/component/feature/form-add-container.jsx
@@ -32,6 +32,7 @@ const prepare = (methods, dispatch) => {
};
methods.addStrategy = (v) => {
+ v.id = Math.round(Math.random() * 10000000);
methods.pushToList('strategies', v);
};
@@ -39,12 +40,15 @@ const prepare = (methods, dispatch) => {
methods.updateInList('strategies', index, n);
};
+ methods.moveStrategy = (index, toIndex) => {
+ methods.moveItem('strategies', index, toIndex);
+ };
+
methods.removeStrategy = (index) => {
methods.removeFromList('strategies', index);
};
- methods.validateName = (v) => {
- const featureToggleName = v.target.value;
+ methods.validateName = (featureToggleName) => {
validateName(featureToggleName)
.then(() => methods.setValue('nameError', undefined))
.catch((err) => methods.setValue('nameError', err.message));
diff --git a/frontend/src/component/feature/form-edit-container.jsx b/frontend/src/component/feature/form-edit-container.jsx
index 7f592804f8..f19d36c67a 100644
--- a/frontend/src/component/feature/form-edit-container.jsx
+++ b/frontend/src/component/feature/form-edit-container.jsx
@@ -13,7 +13,12 @@ function getId (props) {
// best is to emulate the "input-storage"?
const mapStateToProps = createMapper({
id: getId,
- getDefault: (state, ownProps) => ownProps.featureToggle,
+ getDefault: (state, ownProps) => {
+ ownProps.featureToggle.strategies.forEach((strategy, index) => {
+ strategy.id = Math.round(Math.random() * 1000000 * (1 + index));
+ });
+ return ownProps.featureToggle;
+ },
prepare: (props) => {
props.editmode = true;
return props;
@@ -38,6 +43,7 @@ const prepare = (methods, dispatch) => {
};
methods.addStrategy = (v) => {
+ v.id = Math.round(Math.random() * 10000000);
methods.pushToList('strategies', v);
};
@@ -45,6 +51,10 @@ const prepare = (methods, dispatch) => {
methods.removeFromList('strategies', index);
};
+ methods.moveStrategy = (index, toIndex) => {
+ methods.moveItem('strategies', index, toIndex);
+ };
+
methods.updateStrategy = (index, n) => {
methods.updateInList('strategies', index, n);
};
diff --git a/frontend/src/component/feature/form/index.jsx b/frontend/src/component/feature/form/index.jsx
index ca570a41e2..40417d4e1a 100644
--- a/frontend/src/component/feature/form/index.jsx
+++ b/frontend/src/component/feature/form/index.jsx
@@ -29,6 +29,7 @@ class AddFeatureToggleComponent extends Component {
addStrategy,
removeStrategy,
updateStrategy,
+ moveStrategy,
onSubmit,
onCancel,
editmode = false,
@@ -81,6 +82,7 @@ class AddFeatureToggleComponent extends Component {
configuredStrategies={configuredStrategies}
addStrategy={addStrategy}
updateStrategy={updateStrategy}
+ moveStrategy={moveStrategy}
removeStrategy={removeStrategy} />
diff --git a/frontend/src/component/feature/form/strategies-list.jsx b/frontend/src/component/feature/form/strategies-list.jsx
index 1c4b04a576..d8d752ae5f 100644
--- a/frontend/src/component/feature/form/strategies-list.jsx
+++ b/frontend/src/component/feature/form/strategies-list.jsx
@@ -1,6 +1,9 @@
import React, { PropTypes } from 'react';
import ConfigureStrategy from './strategy-configure';
+import { DragDropContext } from 'react-dnd';
+import HTML5Backend from 'react-dnd-html5-backend';
+@DragDropContext(HTML5Backend) // eslint-disable-line new-cap
class StrategiesList extends React.Component {
static propTypes () {
@@ -9,6 +12,7 @@ class StrategiesList extends React.Component {
configuredStrategies: PropTypes.array.isRequired,
updateStrategy: PropTypes.func.isRequired,
removeStrategy: PropTypes.func.isRequired,
+ moveStrategy: PropTypes.func.isRequired,
};
}
@@ -16,6 +20,9 @@ class StrategiesList extends React.Component {
const {
strategies,
configuredStrategies,
+ moveStrategy,
+ removeStrategy,
+ updateStrategy,
} = this.props;
if (!configuredStrategies || configuredStrategies.length === 0) {
@@ -24,10 +31,12 @@ class StrategiesList extends React.Component {
const blocks = configuredStrategies.map((strategy, i) => (
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 3da5d7bc18..d484169fe4 100644
--- a/frontend/src/component/feature/form/strategy-configure.jsx
+++ b/frontend/src/component/feature/form/strategy-configure.jsx
@@ -3,7 +3,8 @@ import {
Textfield, Button,
Card, CardTitle, CardText, CardActions, CardMenu,
IconButton, Icon,
-} from 'react-mdl';
+} from 'react-mdl';
+import { DragSource, DropTarget } from 'react-dnd';
import { Link } from 'react-router';
import StrategyInputPercentage from './strategy-input-percentage';
import StrategyInputList from './strategy-input-list';
@@ -13,7 +14,6 @@ const style = {
minWidth: '300px',
maxWidth: '100%',
margin: '5px 20px 15px 0px',
- background: '#f2f9fc',
};
const helpText = {
@@ -21,6 +21,42 @@ const helpText = {
fontSize: '12px',
lineHeight: '14px',
};
+
+
+const dragSource = {
+ beginDrag (props) {
+ return {
+ id: props.id,
+ index: props.index,
+ };
+ },
+ endDrag (props, monitor) {
+ if (!monitor.didDrop()) {
+ return;
+ }
+ const result = monitor.getDropResult();
+ if (typeof result.index === 'number' && props.index !== result.index) {
+ props.moveStrategy(props.index, result.index);
+ }
+ },
+};
+
+const dragTarget = {
+ drop (props) {
+ return {
+ index: props.index,
+ };
+ },
+};
+
+@DropTarget('strategy', dragTarget, connect => ({ // eslint-disable-line new-cap
+ connectDropTarget: connect.dropTarget(),
+}))
+@DragSource('strategy', dragSource, (connect, monitor) => ({ // eslint-disable-line new-cap
+ connectDragSource: connect.dragSource(),
+ connectDragPreview: connect.dragPreview(),
+ isDragging: monitor.isDragging(),
+}))
class StrategyConfigure extends React.Component {
static propTypes () {
@@ -29,13 +65,14 @@ class StrategyConfigure extends React.Component {
strategyDefinition: PropTypes.object.isRequired,
updateStrategy: PropTypes.func.isRequired,
removeStrategy: PropTypes.func.isRequired,
+ moveStrategy: PropTypes.func.isRequired,
+ isDragging: PropTypes.bool.isRequired,
+ connectDragPreview: PropTypes.func.isRequired,
+ connectDragSource: PropTypes.func.isRequired,
+ connectDropTarget: PropTypes.func.isRequired,
};
}
- // shouldComponentUpdate (props, nextProps) {
- // console.log({ props, nextProps });
- // }
-
handleConfigChange = (key, e) => {
this.setConfig(key, e.target.value);
};
@@ -98,7 +135,7 @@ class StrategyConfigure extends React.Component {
label={name}
onChange={this.handleConfigChange.bind(this, name)}
value={value}
- />
+ />
{description && {description}
}
);
@@ -114,7 +151,7 @@ class StrategyConfigure extends React.Component {
label={name}
onChange={this.handleConfigChange.bind(this, name)}
value={value}
- />
+ />
{description && {description}
}
);
@@ -125,9 +162,46 @@ class StrategyConfigure extends React.Component {
}
render () {
- if (!this.props.strategyDefinition) {
+ const { isDragging, connectDragPreview, connectDragSource, connectDropTarget } = this.props;
+
+ let item;
+ if (this.props.strategyDefinition) {
+ const inputFields = this.renderInputFields(this.props.strategyDefinition);
const { name } = this.props.strategy;
- return (
+ item = (
+
+
+ {name}
+
+
+ {this.props.strategyDefinition.description}
+
+ {
+ inputFields &&
+ {inputFields}
+
+ }
+
+
+
+
+
+
+ {connectDragSource(
+ )}
+
+
+ );
+ } else {
+ const { name } = this.props.strategy;
+ item = (
"{name}" deleted?
@@ -142,35 +216,9 @@ class StrategyConfigure extends React.Component {
);
}
- const inputFields = this.renderInputFields(this.props.strategyDefinition);
-
- const { name } = this.props.strategy;
-
- return (
-
-
- { name }
-
-
- {this.props.strategyDefinition.description}
-
- {
- inputFields &&
- {inputFields}
-
- }
-
-
-
-
-
-
-
-
- );
+ return (connectDropTarget(connectDragPreview(
+ {item}
+ )));
}
}
diff --git a/frontend/src/component/input-helpers.js b/frontend/src/component/input-helpers.js
index c0b24790bc..d3c734422c 100644
--- a/frontend/src/component/input-helpers.js
+++ b/frontend/src/component/input-helpers.js
@@ -6,6 +6,7 @@ import {
createPush,
createUp,
createInit,
+ createMove,
} from '../store/input-actions';
function getId (id, ownProps) {
@@ -57,6 +58,10 @@ export function createActions ({ id, prepare = (v) => v }) {
dispatch(createPop({ id: getId(id, ownProps), key, index }));
},
+ moveItem (key, index, toIndex) {
+ dispatch(createMove({ id: getId(id, ownProps), key, index, toIndex }));
+ },
+
updateInList (key, index, newValue, merge = false) {
dispatch(createUp({ id: getId(id, ownProps), key, index, newValue, merge }));
},
diff --git a/frontend/src/jsconfig.json b/frontend/src/jsconfig.json
new file mode 100644
index 0000000000..504cd646e1
--- /dev/null
+++ b/frontend/src/jsconfig.json
@@ -0,0 +1,5 @@
+{
+ "compilerOptions": {
+ "experimentalDecorators": true
+ }
+}
diff --git a/frontend/src/store/input-actions.js b/frontend/src/store/input-actions.js
index 5b6f5bead6..39dbd3d707 100644
--- a/frontend/src/store/input-actions.js
+++ b/frontend/src/store/input-actions.js
@@ -6,6 +6,7 @@ export const actions = {
LIST_UP: 'LIST_UP',
CLEAR: 'CLEAR',
INIT: 'INIT',
+ MOVE: 'MOVE',
};
export const createInit = ({ id, value }) => ({ type: actions.INIT, id, value });
@@ -13,6 +14,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 createMove = ({ id, key, index, toIndex }) => ({ type: actions.MOVE, id, key, index, toIndex });
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 });
diff --git a/frontend/src/store/input-store.js b/frontend/src/store/input-store.js
index 743c7c570f..cc0a3c7075 100644
--- a/frontend/src/store/input-store.js
+++ b/frontend/src/store/input-store.js
@@ -69,6 +69,13 @@ function removeFromList (state, { id, key, index }) {
return state.updateIn(id.concat([key]), (list) => list.remove(index));
}
+function move (state, { id, key, index, toIndex }) {
+ return state.updateIn(id.concat([key]), list => {
+ const olditem = list.get(index);
+ return list.delete(index).insert(toIndex, olditem);
+ });
+}
+
const inputState = (state = getInitState(), action) => {
if (!action.id) {
return state;
@@ -88,6 +95,8 @@ const inputState = (state = getInitState(), action) => {
return addToList(state, action);
case actions.LIST_POP:
return removeFromList(state, action);
+ case actions.MOVE:
+ return move(state, action);
case actions.LIST_UP:
return updateInList(state, action);
case actions.CLEAR:
diff --git a/frontend/typings.json b/frontend/typings.json
index a00f27177e..2f1203b9cc 100644
--- a/frontend/typings.json
+++ b/frontend/typings.json
@@ -1,5 +1,6 @@
{
"globalDependencies": {
+ "react-dnd": "registry:dt/react-dnd#2.0.2+20161111212335",
"react-mdl": "registry:dt/react-mdl#0.0.0+20160830142027"
}
}