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 } + renderValue={selectedP => ( +
+ {selectedP.map(parent => ( + handleParentChange(null, parent.id)} + deleteIcon={} + /> + ))} +
+ )} > - {parentOptions} + {selectableParents.map(parentObj => ( + + + + ))}
@@ -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, }), }