diff --git a/src/actionTypes/index.js b/src/actionTypes/index.js
index 84c2219..9b80c2c 100644
--- a/src/actionTypes/index.js
+++ b/src/actionTypes/index.js
@@ -2,8 +2,7 @@ export const LOAD_INIT_DATA = 'LOAD_INIT_DATA';
export const ADD_COMPONENT = 'ADD_COMPONENT';
export const UPDATE_COMPONENT = 'UPDATE_COMPONENT';
export const DELETE_COMPONENT = 'DELETE_COMPONENT';
-export const ADD_NEW_CHILD = 'ADD_NEW_CHILD';
-export const DELETE_CHILD = 'DELETE_CHILD';
+export const UPDATE_CHILDREN = 'UPDATE_CHILDREN';
export const REASSIGN_PARENT = 'REASSIGN_PARENT';
export const SET_SELECTABLE_PARENTS = 'SET_SELECTABLE_PARENTS';
export const EXPORT_FILES = 'EXPORT_FILES';
diff --git a/src/actions/components.js b/src/actions/components.js
index 09747c5..3db7e83 100644
--- a/src/actions/components.js
+++ b/src/actions/components.js
@@ -3,8 +3,7 @@ import {
ADD_COMPONENT,
UPDATE_COMPONENT,
DELETE_COMPONENT,
- ADD_NEW_CHILD,
- DELETE_CHILD,
+ UPDATE_CHILDREN,
REASSIGN_PARENT,
SET_SELECTABLE_PARENTS,
EXPORT_FILES,
@@ -40,30 +39,21 @@ export const loadInitData = () => (dispatch) => {
}));
};
-export const addNewChild = (({
- id, childIndex, childId,
+export const updateChildren = (({
+ parentIds, childIndex, childId,
}) => ({
- type: ADD_NEW_CHILD,
+ type: UPDATE_CHILDREN,
payload: {
- id, childIndex, childId,
+ parentIds, childIndex, childId,
},
}));
-export const deleteChild = (({
- parent, childIndex, childId,
-}) => ({
- type: DELETE_CHILD,
- payload: {
- parent, childIndex, childId,
- },
-}));
-
-export const parentReassignment = (({ index, id, parent }) => ({
+export const parentReassignment = (({ index, id, parentIds }) => ({
type: REASSIGN_PARENT,
payload: {
index,
id,
- parent,
+ parentIds,
},
}));
@@ -72,19 +62,20 @@ export const addComponent = ({ title }) => (dispatch) => {
dispatch({ type: SET_SELECTABLE_PARENTS });
};
-export const deleteComponent = ({ index, id, parent }) => (dispatch) => {
- // Delete Component from its parent if it has a parent.
- if (parent && parent.id) {
- dispatch(deleteChild({ parent, childId: id, childIndex: index }));
+export const deleteComponent = ({ index, id, parentIds = [] }) => (dispatch) => {
+ if (parentIds.length) {
+ // Delete Component from its parent if it has a parent.
+ dispatch(updateChildren({ parentIds, childId: id, childIndex: index }));
}
// Reassign Component's children to its parent if it has one or make them orphans
- dispatch(parentReassignment({ index, id, parent }));
+ dispatch(parentReassignment({ index, id, parentIds }));
+
dispatch({ type: DELETE_COMPONENT, payload: { index, id } });
dispatch({ type: SET_SELECTABLE_PARENTS });
};
export const updateComponent = ({
- id, index, parent = null, newParentId = null, color = null, stateful = null,
+ id, index, newParentId = null, color = null, stateful = null,
}) => (dispatch) => {
dispatch({
type: UPDATE_COMPONENT,
@@ -93,12 +84,8 @@ export const updateComponent = ({
},
});
- if (newParentId && newParentId !== 'null') {
- dispatch(addNewChild({ id: newParentId, childId: id, childIndex: index }));
- }
-
- if (parent && parent.id) {
- dispatch(deleteChild({ parent, index, childId: id }));
+ if (newParentId) {
+ dispatch(updateChildren({ parentIds: [newParentId], childId: id, childIndex: index }));
}
dispatch({ type: SET_SELECTABLE_PARENTS });
diff --git a/src/components/LeftColExpansionPanel.jsx b/src/components/LeftColExpansionPanel.jsx
index 94dbc30..c0f9402 100644
--- a/src/components/LeftColExpansionPanel.jsx
+++ b/src/components/LeftColExpansionPanel.jsx
@@ -7,8 +7,12 @@ import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails';
import ExpansionPanelActions from '@material-ui/core/ExpansionPanelActions';
import Typography from '@material-ui/core/Typography';
import Input from '@material-ui/core/Input';
+import MenuItem from '@material-ui/core/MenuItem';
+import RemoveCircleOutlineIcon from '@material-ui/icons/RemoveCircleOutline';
+import ListItemText from '@material-ui/core/ListItemText';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import Switch from '@material-ui/core/Switch';
+import Chip from '@material-ui/core/Chip';
import IconButton from '@material-ui/core/IconButton';
import DeleteIcon from '@material-ui/icons/Delete';
import FlipToBackIcon from '@material-ui/icons/FlipToBack';
@@ -28,6 +32,13 @@ const styles = theme => ({
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
},
+ chips: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ chip: {
+ margin: theme.spacing.unit / 4,
+ },
panel: {
backgroundColor: '#333333',
},
@@ -60,6 +71,15 @@ const styles = theme => ({
group: {
margin: `${theme.spacing.unit}px 0`,
},
+ icon: {
+ fontSize: '20px',
+ color: '#000',
+ transition: 'all .2s ease',
+
+ '&:hover': {
+ color: 'red',
+ },
+ },
});
const LeftColExpansionPanel = (props) => {
@@ -79,23 +99,24 @@ const LeftColExpansionPanel = (props) => {
id,
stateful,
color,
- parent,
+ parents,
+ parentIds,
selectableParents,
} = component;
- const parentOptions = [
- ,
- ...selectableParents.map(
- selectableParent => ,
- ),
- ];
+ const handleParentChange = (event, parentId = null) => {
+ let newParentId = parentId;
+ if (event) {
+ const selectedParents = event.target.value;
+ newParentId = selectedParents[selectedParents.length - 1].id;
+ }
+
+ return updateComponent({
+ index,
+ id,
+ newParentId,
+ });
+ };
return (
@@ -130,24 +151,35 @@ const LeftColExpansionPanel = (props) => {
/>
-
Parent
+
selectedParents
@@ -173,7 +205,7 @@ const LeftColExpansionPanel = (props) => {
className={classes.button}
onClick={() => {
deleteComponent({
- index, id, parent,
+ index, id, parentIds,
});
}}
aria-label='Delete'>
diff --git a/src/components/SortableComponent.jsx b/src/components/SortableComponent.jsx
index ee13fe3..ea29133 100644
--- a/src/components/SortableComponent.jsx
+++ b/src/components/SortableComponent.jsx
@@ -5,15 +5,16 @@ import 'react-sortable-tree/style.css';
const SortableComponent = (props) => {
const rootComponents = props.components.filter(
- comp => comp.parentId.length === 0,
+ comp => comp.parentIds.length === 0,
).reverse();
+
return (
this.setState({ treeData })}
+ onChange={() => {}}
/>
);
@@ -22,5 +23,5 @@ const SortableComponent = (props) => {
export default SortableComponent;
SortableComponent.propTypes = {
- components: PropTypes.array,
+ components: PropTypes.array.isRequired,
};
diff --git a/src/containers/AppContainer.jsx b/src/containers/AppContainer.jsx
index 549dc65..08d4b9e 100644
--- a/src/containers/AppContainer.jsx
+++ b/src/containers/AppContainer.jsx
@@ -6,7 +6,7 @@ import LinearProgress from '@material-ui/core/LinearProgress';
import LeftContainer from './LeftContainer.jsx';
import MainContainer from './MainContainer.jsx';
import RightContainer from './RightContainer.jsx';
-import convertIdToObjs from '../utils/convertIdsToObjs.util';
+import convertIdsToObjs from '../utils/convertIdsToObjs.util';
import theme from '../components/theme';
import { loadInitData } from '../actions/components';
@@ -51,7 +51,7 @@ class AppContainer extends Component {
loading,
} = this.props;
const { width, rightColumnOpen } = this.state;
- const updatedComponents = convertIdToObjs(components);
+ const updatedComponents = convertIdsToObjs(components);
return (
diff --git a/src/containers/LeftContainer.jsx b/src/containers/LeftContainer.jsx
index a439974..81abf8a 100644
--- a/src/containers/LeftContainer.jsx
+++ b/src/containers/LeftContainer.jsx
@@ -16,20 +16,20 @@ const mapDispatchToProps = dispatch => ({
addComponent: ({ title }) => dispatch(actions.addComponent({ title })),
updateComponent:
({
- id, index, parent = null, newParentId = null, color = null, stateful = null,
+ id, index, newParentId = null, color = null, stateful = null,
}) => dispatch(actions.updateComponent({
- id, index, parent, newParentId, color, stateful,
+ id, index, newParentId, color, stateful,
})),
deleteComponent: ({
- index, id, parent,
- }) => dispatch(actions.deleteComponent({ index, id, parent })),
+ index, id, parentIds,
+ }) => dispatch(actions.deleteComponent({ index, id, parentIds })),
moveToBottom: componentId => dispatch(actions.moveToBottom(componentId)),
moveToTop: componentId => dispatch(actions.moveToTop(componentId)),
openExpansionPanel: component => dispatch(actions.openExpansionPanel(component)),
deleteAllData: () => dispatch(actions.deleteAllData()),
});
-const styles = theme => ({
+const styles = () => ({
cssLabel: {
color: 'white',
diff --git a/src/containers/MainContainer.jsx b/src/containers/MainContainer.jsx
index c58631c..b71ed35 100644
--- a/src/containers/MainContainer.jsx
+++ b/src/containers/MainContainer.jsx
@@ -221,10 +221,13 @@ class MainContainer extends Component {
showGenerateAppModal,
setImage,
} = this;
+ const cursor = this.state.draggable ? 'move' : 'default';
return (
-
+
localforage.setItem('state', state);
-
-export const loadState = () => localforage.getItem('state');
+export const saveState = state => localforage.setItem('state-v1.0.1', state);
+export const loadState = () => localforage.getItem('state-v1.0.1');
diff --git a/src/reducers/componentReducer.js b/src/reducers/componentReducer.js
index d4c1bd9..caf5b09 100644
--- a/src/reducers/componentReducer.js
+++ b/src/reducers/componentReducer.js
@@ -3,8 +3,7 @@ import {
ADD_COMPONENT,
UPDATE_COMPONENT,
DELETE_COMPONENT,
- ADD_NEW_CHILD,
- DELETE_CHILD,
+ UPDATE_CHILDREN,
REASSIGN_PARENT,
SET_SELECTABLE_PARENTS,
EXPORT_FILES,
@@ -28,8 +27,7 @@ import {
addComponent,
updateComponent,
deleteComponent,
- addChild,
- deleteChild,
+ updateChildren,
reassignParent,
setSelectableP,
exportFilesSuccess,
@@ -74,10 +72,8 @@ const componentReducer = (state = initialApplicationState, action) => {
return updateComponent(state, action.payload);
case DELETE_COMPONENT:
return deleteComponent(state, action.payload);
- case ADD_NEW_CHILD:
- return addChild(state, action.payload);
- case DELETE_CHILD:
- return deleteChild(state, action.payload);
+ case UPDATE_CHILDREN:
+ return updateChildren(state, action.payload);
case REASSIGN_PARENT:
return reassignParent(state, action.payload);
case SET_SELECTABLE_PARENTS:
diff --git a/src/utils/componentReducer.util.js b/src/utils/componentReducer.util.js
index 74b8e93..5624455 100644
--- a/src/utils/componentReducer.util.js
+++ b/src/utils/componentReducer.util.js
@@ -5,7 +5,7 @@ const initialComponentState = {
id: null,
stateful: false,
title: '',
- parentId: '',
+ parentIds: [],
color: getColor(),
draggable: true,
childrenIds: [],
@@ -57,10 +57,14 @@ export const updateComponent = ((state, {
const components = state.components.map((comp) => {
if (comp.id === id) {
component = { ...comp };
- if (newParentId === 'null') {
- component.parentId = '';
- } else if (newParentId) {
- component.parentId = newParentId;
+ if (newParentId) {
+ const parentIdsSet = new Set(component.parentIds);
+ if (parentIdsSet.has(newParentId)) {
+ parentIdsSet.delete(newParentId);
+ } else {
+ parentIdsSet.add(newParentId);
+ }
+ component.parentIds = [...parentIdsSet];
}
if (props) {
component.props = props;
@@ -98,27 +102,20 @@ export const deleteComponent = (state, { index, id }) => {
};
};
-export const addChild = ((state, { id, childId }) => {
+// Add or remove children
+export const updateChildren = ((state, { parentIds, childId }) => {
const components = state.components.map((component) => {
- if (component.id === id) {
- const { childrenIds } = component;
- return { ...component, childrenIds: [...childrenIds, childId] };
- }
- return component;
- });
-
- return {
- ...state,
- components,
- };
-});
+ if (parentIds.includes(component.id)) {
+ const parentComp = { ...component };
+ const childrenIdsSet = new Set(parentComp.childrenIds);
+ if (childrenIdsSet.has(childId)) {
+ childrenIdsSet.delete(childId);
+ } else {
+ childrenIdsSet.add(childId);
+ }
-export const deleteChild = ((state, { parent, childId }) => {
- const components = state.components.map((component) => {
- if (component.id === parent.id) {
- // Find child with matching id and remove from children
- const childrenIds = component.childrenIds.filter(id => id !== childId);
- return { ...component, childrenIds };
+ parentComp.childrenIds = [...childrenIdsSet];
+ return parentComp;
}
return component;
});
@@ -158,16 +155,22 @@ export const changeImagePath = (state, imagePath) => ({
imagePath,
});
-export const reassignParent = ((state, { index, parent = {} }) => {
+
+// Assign comp's children to comp's parent
+export const reassignParent = ((state, { index, id, parentIds = [] }) => {
// Get all childrenIds of the component to be deleted
const { childrenIds } = state.components[index];
const components = state.components.map((comp) => {
// Give each child their previous parent's parent
if (childrenIds.includes(comp.id)) {
- return { ...comp, parentId: parent.id || '' };
+ const prevParentIds = comp.parentIds.filter(parentId => parentId !== id);
+ return {
+ ...comp,
+ parentIds: [...new Set(prevParentIds.concat(parentIds))],
+ };
}
// Give the parent all children of it's to be deleted child
- if (parent.id === comp.id) {
+ if (parentIds.includes(comp.id)) {
const prevChildrenIds = comp.childrenIds;
return { ...comp, childrenIds: [...new Set(prevChildrenIds.concat(childrenIds))] };
}
diff --git a/src/utils/convertIdsToObjs.util.js b/src/utils/convertIdsToObjs.util.js
index 6f00e50..db98f3b 100644
--- a/src/utils/convertIdsToObjs.util.js
+++ b/src/utils/convertIdsToObjs.util.js
@@ -1,20 +1,38 @@
-const convertChildAndParentIds = (components, component) => {
- const childrenAsObj = component.childrenIds.map((id) => {
- const child = components.find(comp => id === comp.id);
- if (!child.children || (child.childrenIds.length !== child.children.length)) {
- return convertChildAndParentIds(components, child);
- }
- if (!child.parent || (child.parentId.length !== child.parent.length)) {
- return convertChildAndParentIds(components, child);
- }
- return child;
- });
- const parentAsObj = components.find(parent => parent.id === component.parentId) || {};
- return { ...component, children: childrenAsObj, parent: parentAsObj };
+let convertChildAndParentIds;
+const convertComponentsArrToObj = components => components.reduce(
+ (obj, comp) => {
+ const compsObj = obj;
+ compsObj[comp.id] = comp;
+ return compsObj;
+ }, {},
+);
+
+const convertCompIdToObj = (id, compsObj) => {
+ const comp = compsObj[id];
+ if (!comp.children || (comp.childrenIds.length !== comp.children.length)) {
+ return convertChildAndParentIds(compsObj, comp);
+ }
+ if (!comp.parents || (comp.parentIds.length !== comp.parents.length)) {
+ return convertChildAndParentIds(compsObj, comp);
+ }
+ return comp;
};
-const convertIdToObjs = components => components.map(
- component => convertChildAndParentIds(components, component),
-);
+convertChildAndParentIds = (compsObj, component) => {
+ const childrenAsObj = component.childrenIds
+ .map(id => convertCompIdToObj(id, compsObj));
+
+ const parentsAsObj = component.parentIds
+ .map(id => compsObj[id]);
+
+ return { ...component, children: childrenAsObj, parents: parentsAsObj };
+};
+
+const convertIdsToObjs = (components) => {
+ const compsObj = convertComponentsArrToObj(components);
+ return components.map(
+ component => convertChildAndParentIds(compsObj, component),
+ );
+};
-export default convertIdToObjs;
+export default convertIdsToObjs;
diff --git a/src/utils/setSelectableParents.util.js b/src/utils/setSelectableParents.util.js
index 0d2d1bd..24d3582 100644
--- a/src/utils/setSelectableParents.util.js
+++ b/src/utils/setSelectableParents.util.js
@@ -10,8 +10,11 @@ const getAllChildren = (components, childrenIds, unSelectable = []) => {
return unSelectable;
};
-const getSelectableParents = ({ id, childrenIds, components }) => {
- const unSelectableParents = getAllChildren(components, childrenIds, [id]);
+const getSelectableParents = ({
+ id, childrenIds,
+ parentIds, components,
+}) => {
+ const unSelectableParents = getAllChildren(components, childrenIds, [id, ...parentIds]);
return components
.filter(comp => !unSelectableParents.includes(comp.id));
};
@@ -23,6 +26,7 @@ const setSelectableParents = components => components.map(
selectableParents: getSelectableParents({
id: comp.id,
childrenIds: comp.childrenIds,
+ parentIds: comp.parentIds,
components,
}),
}