blob: ffc56132a83c5d92835d57cab2050e4a1d74c2e0 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
DASHBOARD_ROOT_ID,
DASHBOARD_GRID_ID,
NEW_COMPONENTS_SOURCE_ID,
DASHBOARD_HEADER_ID,
} from '../util/constants';
import componentIsResizable from '../util/componentIsResizable';
import findParentId from '../util/findParentId';
import getComponentWidthFromDrop from '../util/getComponentWidthFromDrop';
import updateComponentParentsList from '../util/updateComponentParentsList';
import newComponentFactory from '../util/newComponentFactory';
import newEntitiesFromDrop from '../util/newEntitiesFromDrop';
import reorderItem from '../util/dnd-reorder';
import shouldWrapChildInRow from '../util/shouldWrapChildInRow';
import { ROW_TYPE, TAB_TYPE, TABS_TYPE } from '../util/componentTypes';
import {
UPDATE_COMPONENTS,
UPDATE_COMPONENTS_PARENTS_LIST,
DELETE_COMPONENT,
CREATE_COMPONENT,
MOVE_COMPONENT,
CREATE_TOP_LEVEL_TABS,
DELETE_TOP_LEVEL_TABS,
DASHBOARD_TITLE_CHANGED,
} from '../actions/dashboardLayout';
const actionHandlers = {
[UPDATE_COMPONENTS](state, action) {
const {
payload: { nextComponents },
} = action;
return {
...state,
...nextComponents,
};
},
[DELETE_COMPONENT](state, action) {
const {
payload: { id, parentId },
} = action;
if (!parentId || !id || !state[id] || !state[parentId]) return state;
const nextComponents = { ...state };
function recursivelyDeleteChildren(componentId, componentParentId) {
// delete child and it's children
const component = nextComponents[componentId];
delete nextComponents[componentId];
const { children = [] } = component;
children.forEach(childId => {
recursivelyDeleteChildren(childId, componentId);
});
const parent = nextComponents[componentParentId];
if (parent) {
// may have been deleted in another recursion
const componentIndex = (parent.children || []).indexOf(componentId);
if (componentIndex > -1) {
const nextChildren = [...parent.children];
nextChildren.splice(componentIndex, 1);
nextComponents[componentParentId] = {
...parent,
children: nextChildren,
};
}
}
}
recursivelyDeleteChildren(id, parentId);
const nextParent = nextComponents[parentId];
if (nextParent.type === ROW_TYPE && nextParent.children.length === 0) {
const grandparentId = findParentId({
childId: parentId,
layout: nextComponents,
});
recursivelyDeleteChildren(parentId, grandparentId);
}
return nextComponents;
},
[CREATE_COMPONENT](state, action) {
const {
payload: { dropResult },
} = action;
const newEntities = newEntitiesFromDrop({ dropResult, layout: state });
return {
...state,
...newEntities,
};
},
[MOVE_COMPONENT](state, action) {
const {
payload: { dropResult },
} = action;
const { source, destination, dragging } = dropResult;
if (!source || !destination || !dragging) return state;
const nextEntities = reorderItem({
entitiesMap: state,
source,
destination,
});
if (componentIsResizable(nextEntities[dragging.id])) {
// update component width if it changed
const nextWidth =
getComponentWidthFromDrop({
dropResult,
layout: state,
}) || undefined; // don't set a 0 width
if ((nextEntities[dragging.id].meta || {}).width !== nextWidth) {
nextEntities[dragging.id] = {
...nextEntities[dragging.id],
meta: {
...nextEntities[dragging.id].meta,
width: nextWidth,
},
};
}
}
// wrap the dragged component in a row depending on destination type
const wrapInRow = shouldWrapChildInRow({
parentType: destination.type,
childType: dragging.type,
});
if (wrapInRow) {
const destinationEntity = nextEntities[destination.id];
const destinationChildren = destinationEntity.children;
const newRow = newComponentFactory(ROW_TYPE);
newRow.children = [destinationChildren[destination.index]];
newRow.parents = (destinationEntity.parents || []).concat(destination.id);
destinationChildren[destination.index] = newRow.id;
nextEntities[newRow.id] = newRow;
}
return {
...state,
...nextEntities,
};
},
[CREATE_TOP_LEVEL_TABS](state, action) {
const {
payload: { dropResult },
} = action;
const { source, dragging } = dropResult;
// move children of current root to be children of the dragging tab
const rootComponent = state[DASHBOARD_ROOT_ID];
const topLevelId = rootComponent.children[0];
const topLevelComponent = state[topLevelId];
if (source.id !== NEW_COMPONENTS_SOURCE_ID) {
// component already exists
const draggingTabs = state[dragging.id];
const draggingTabId = draggingTabs.children[0];
const draggingTab = state[draggingTabId];
// move all children except the one that is dragging
const childrenToMove = [...topLevelComponent.children].filter(
id => id !== dragging.id,
);
return {
...state,
[DASHBOARD_ROOT_ID]: {
...rootComponent,
children: [dragging.id],
},
[topLevelId]: {
...topLevelComponent,
children: [],
},
[draggingTabId]: {
...draggingTab,
children: [...draggingTab.children, ...childrenToMove],
},
};
}
// create new component
const newEntities = newEntitiesFromDrop({ dropResult, layout: state });
const newEntitiesArray = Object.values(newEntities);
const tabComponent = newEntitiesArray.find(
component => component.type === TAB_TYPE,
);
const tabsComponent = newEntitiesArray.find(
component => component.type === TABS_TYPE,
);
tabComponent.children = [...topLevelComponent.children];
newEntities[topLevelId] = { ...topLevelComponent, children: [] };
newEntities[DASHBOARD_ROOT_ID] = {
...rootComponent,
children: [tabsComponent.id],
};
return {
...state,
...newEntities,
};
},
[DELETE_TOP_LEVEL_TABS](state) {
const rootComponent = state[DASHBOARD_ROOT_ID];
const topLevelId = rootComponent.children[0];
const topLevelTabs = state[topLevelId];
if (topLevelTabs.type !== TABS_TYPE) return state;
let childrenToMove = [];
const nextEntities = { ...state };
topLevelTabs.children.forEach(tabId => {
const tabComponent = state[tabId];
childrenToMove = [...childrenToMove, ...tabComponent.children];
delete nextEntities[tabId];
});
delete nextEntities[topLevelId];
nextEntities[DASHBOARD_ROOT_ID] = {
...rootComponent,
children: [DASHBOARD_GRID_ID],
};
nextEntities[DASHBOARD_GRID_ID] = {
...state[DASHBOARD_GRID_ID],
children: childrenToMove,
};
return nextEntities;
},
[UPDATE_COMPONENTS_PARENTS_LIST](state) {
const nextState = {
...state,
};
updateComponentParentsList({
currentComponent: nextState[DASHBOARD_ROOT_ID],
layout: nextState,
});
return {
...nextState,
};
},
[DASHBOARD_TITLE_CHANGED](state, action) {
return {
...state,
[DASHBOARD_HEADER_ID]: {
...state[DASHBOARD_HEADER_ID],
meta: {
...state[DASHBOARD_HEADER_ID].meta,
text: action.text,
},
},
};
},
};
export default function layoutReducer(state = {}, action) {
if (action.type in actionHandlers) {
const handler = actionHandlers[action.type];
return handler(state, action);
}
return state;
}