2017-11-09 17:10:30 -08:00
|
|
|
import _ from 'lodash';
|
2017-11-29 15:44:51 -08:00
|
|
|
import invariant from 'invariant';
|
2017-11-09 17:10:30 -08:00
|
|
|
import {
|
|
|
|
composeReducers,
|
|
|
|
createAction,
|
|
|
|
createTypes,
|
|
|
|
handleActions
|
|
|
|
} from 'berkeleys-redux-utils';
|
2017-07-31 20:04:01 -07:00
|
|
|
|
2017-12-29 12:29:03 +00:00
|
|
|
import { types as challengeTypes } from '../../routes/Challenges/redux';
|
2017-07-31 20:04:01 -07:00
|
|
|
import ns from '../ns.json';
|
|
|
|
|
|
|
|
import windowEpic from './window-epic.js';
|
|
|
|
import dividerEpic from './divider-epic.js';
|
|
|
|
|
|
|
|
export const epics = [
|
|
|
|
windowEpic,
|
|
|
|
dividerEpic
|
|
|
|
];
|
|
|
|
|
|
|
|
export const types = createTypes([
|
2017-11-29 15:44:51 -08:00
|
|
|
'panesMapUpdated',
|
2017-07-31 20:04:01 -07:00
|
|
|
'panesMounted',
|
|
|
|
'panesUpdated',
|
|
|
|
'panesWillMount',
|
|
|
|
'panesWillUnmount',
|
|
|
|
'updateSize',
|
|
|
|
|
|
|
|
'dividerClicked',
|
|
|
|
'dividerMoved',
|
|
|
|
'mouseReleased',
|
|
|
|
'windowResized',
|
|
|
|
|
|
|
|
// commands
|
2017-12-29 12:29:03 +00:00
|
|
|
'hidePane',
|
|
|
|
'updateNavHeight'
|
2017-07-31 20:04:01 -07:00
|
|
|
], ns);
|
|
|
|
|
2017-11-29 15:44:51 -08:00
|
|
|
export const panesMapUpdated = createAction(
|
|
|
|
types.panesMapUpdated,
|
2017-11-09 17:10:30 -08:00
|
|
|
null,
|
2017-11-29 15:44:51 -08:00
|
|
|
(type, panesMap) => ({ trigger: type, panesMap })
|
2017-11-09 17:10:30 -08:00
|
|
|
);
|
2017-07-31 20:04:01 -07:00
|
|
|
export const panesMounted = createAction(types.panesMounted);
|
|
|
|
export const panesUpdated = createAction(types.panesUpdated);
|
|
|
|
export const panesWillMount = createAction(types.panesWillMount);
|
|
|
|
export const panesWillUnmount = createAction(types.panesWillUnmount);
|
|
|
|
|
|
|
|
export const dividerClicked = createAction(types.dividerClicked);
|
|
|
|
export const dividerMoved = createAction(types.dividerMoved);
|
|
|
|
export const mouseReleased = createAction(types.mouseReleased);
|
|
|
|
export const windowResized = createAction(types.windowResized);
|
|
|
|
|
|
|
|
// commands
|
|
|
|
export const hidePane = createAction(types.hidePane);
|
2017-12-29 12:29:03 +00:00
|
|
|
export const updateNavHeight = createAction(types.updateNavHeight);
|
2017-07-31 20:04:01 -07:00
|
|
|
|
2017-11-29 15:44:51 -08:00
|
|
|
const defaultState = {
|
2017-07-31 20:04:01 -07:00
|
|
|
height: 600,
|
|
|
|
width: 800,
|
|
|
|
navHeight: 50,
|
2017-12-29 12:29:03 +00:00
|
|
|
isMapPaneHidden: false,
|
2017-07-31 20:04:01 -07:00
|
|
|
panes: [],
|
|
|
|
panesByName: {},
|
|
|
|
pressedDivider: null,
|
2017-11-29 15:44:51 -08:00
|
|
|
panesMap: {}
|
2017-07-31 20:04:01 -07:00
|
|
|
};
|
|
|
|
export const getNS = state => state[ns];
|
|
|
|
export const heightSelector = state => {
|
|
|
|
const { navHeight, height } = getNS(state);
|
|
|
|
return height - navHeight;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const panesSelector = state => getNS(state).panes;
|
|
|
|
export const panesByNameSelector = state => getNS(state).panesByName;
|
|
|
|
export const pressedDividerSelector =
|
|
|
|
state => getNS(state).pressedDivider;
|
|
|
|
export const widthSelector = state => getNS(state).width;
|
2017-11-29 15:44:51 -08:00
|
|
|
export const panesMapSelector = state => getNS(state).panesMap;
|
2017-07-31 20:04:01 -07:00
|
|
|
|
2017-11-29 15:44:51 -08:00
|
|
|
function isPanesAction({ type } = {}, panesMap) {
|
|
|
|
return !!panesMap[type];
|
2017-07-31 20:04:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function getDividerLeft(numOfPanes, index) {
|
|
|
|
let dividerLeft = null;
|
|
|
|
if (numOfPanes > 1 && numOfPanes !== index + 1) {
|
|
|
|
dividerLeft = (100 / numOfPanes) * (index + 1);
|
|
|
|
}
|
|
|
|
return dividerLeft;
|
|
|
|
}
|
|
|
|
|
2017-11-29 15:44:51 -08:00
|
|
|
function checkForTypeKeys(panesMap) {
|
|
|
|
_.forEach(panesMap, (_, actionType) => {
|
|
|
|
invariant(
|
|
|
|
actionType !== 'undefined',
|
|
|
|
`action type for ${panesMap[actionType]} is undefined`
|
|
|
|
);
|
2017-11-09 17:10:30 -08:00
|
|
|
});
|
2017-11-29 15:44:51 -08:00
|
|
|
return panesMap;
|
2017-11-09 17:10:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
const getPaneName = (panes, index) => (panes[index] || {}).name || '';
|
|
|
|
|
2017-11-29 15:44:51 -08:00
|
|
|
function normalizePanesMapCreator(createPanesMap) {
|
|
|
|
invariant(
|
|
|
|
_.isFunction(createPanesMap),
|
|
|
|
'createPanesMap should be a function but got %s',
|
|
|
|
createPanesMap
|
|
|
|
);
|
|
|
|
const panesMap = createPanesMap({}, { type: '@@panes/test' });
|
|
|
|
if (typeof panesMap === 'function') {
|
|
|
|
return normalizePanesMapCreator(panesMap);
|
2017-07-31 20:04:01 -07:00
|
|
|
}
|
2017-11-29 15:44:51 -08:00
|
|
|
invariant(
|
|
|
|
!panesMap,
|
|
|
|
'panesMap test should return undefined or null on test action but got %s',
|
|
|
|
panesMap
|
|
|
|
);
|
|
|
|
return createPanesMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
export default function createPanesAspects({ createPanesMap }) {
|
|
|
|
createPanesMap = normalizePanesMapCreator(createPanesMap);
|
2017-07-31 20:04:01 -07:00
|
|
|
|
2017-11-09 17:10:30 -08:00
|
|
|
function middleware({ getState }) {
|
2017-07-31 20:04:01 -07:00
|
|
|
return next => action => {
|
|
|
|
let finalAction = action;
|
2017-11-29 15:44:51 -08:00
|
|
|
const panesMap = panesMapSelector(getState());
|
|
|
|
if (isPanesAction(action, panesMap)) {
|
2017-07-31 20:04:01 -07:00
|
|
|
finalAction = {
|
|
|
|
...action,
|
|
|
|
meta: {
|
|
|
|
...action.meta,
|
2017-11-09 17:10:30 -08:00
|
|
|
isPaneAction: true,
|
2017-11-29 15:44:51 -08:00
|
|
|
paneName: panesMap[action.type]
|
2017-07-31 20:04:01 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2017-11-09 17:10:30 -08:00
|
|
|
const result = next(finalAction);
|
2017-11-29 15:44:51 -08:00
|
|
|
const nextPanesMap = createPanesMap(getState(), action);
|
|
|
|
if (nextPanesMap) {
|
|
|
|
checkForTypeKeys(nextPanesMap);
|
|
|
|
next(panesMapUpdated(action.type, nextPanesMap));
|
2017-11-09 17:10:30 -08:00
|
|
|
}
|
|
|
|
return result;
|
2017-07-31 20:04:01 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-11-09 17:10:30 -08:00
|
|
|
const reducer = composeReducers(
|
|
|
|
ns,
|
|
|
|
handleActions(
|
|
|
|
() => ({
|
|
|
|
[types.dividerClicked]: (state, { payload: name }) => ({
|
|
|
|
...state,
|
|
|
|
pressedDivider: name
|
|
|
|
}),
|
|
|
|
[types.dividerMoved]: (state, { payload: clientX }) => {
|
|
|
|
const { width, pressedDivider: paneName } = state;
|
|
|
|
const dividerBuffer = (200 / width) * 100;
|
|
|
|
const paneIndex =
|
|
|
|
_.findIndex(state.panes, ({ name }) => paneName === name);
|
|
|
|
const currentPane = state.panesByName[paneName];
|
|
|
|
const rightPane =
|
|
|
|
state.panesByName[getPaneName(state.panes, paneIndex + 1)] || {};
|
|
|
|
const leftPane =
|
|
|
|
state.panesByName[getPaneName(state.panes, paneIndex - 1)] || {};
|
|
|
|
const rightBound = (rightPane.dividerLeft || 100) - dividerBuffer;
|
|
|
|
const leftBound = (leftPane.dividerLeft || 0) + dividerBuffer;
|
|
|
|
const newPosition = _.clamp(
|
|
|
|
(clientX / width) * 100,
|
|
|
|
leftBound,
|
|
|
|
rightBound
|
|
|
|
);
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
panesByName: {
|
|
|
|
...state.panesByName,
|
|
|
|
[currentPane.name]: {
|
|
|
|
...currentPane,
|
|
|
|
dividerLeft: newPosition
|
|
|
|
}
|
2017-07-31 20:04:01 -07:00
|
|
|
}
|
2017-11-09 17:10:30 -08:00
|
|
|
};
|
|
|
|
},
|
|
|
|
[types.mouseReleased]: state => ({ ...state, pressedDivider: null }),
|
|
|
|
[types.windowResized]: (state, { payload: { height, width } }) => ({
|
|
|
|
...state,
|
|
|
|
height,
|
|
|
|
width
|
|
|
|
}),
|
|
|
|
// used to clear bin buttons
|
|
|
|
[types.panesWillUnmount]: state => ({
|
|
|
|
...state,
|
|
|
|
panes: [],
|
|
|
|
panesByName: {},
|
|
|
|
pressedDivider: null
|
|
|
|
}),
|
|
|
|
[types.updateNavHeight]: (state, { payload: navHeight }) => ({
|
|
|
|
...state,
|
|
|
|
navHeight
|
2017-12-29 12:29:03 +00:00
|
|
|
}),
|
|
|
|
[challengeTypes.toggleMap]: state => ({
|
|
|
|
...state,
|
|
|
|
isMapPaneHidden: !state.isMapPaneHidden
|
2017-11-09 17:10:30 -08:00
|
|
|
})
|
|
|
|
}),
|
2017-11-29 15:44:51 -08:00
|
|
|
defaultState,
|
2017-11-09 17:10:30 -08:00
|
|
|
),
|
2017-11-29 15:44:51 -08:00
|
|
|
function metaReducer(state = defaultState, action) {
|
|
|
|
if (action.meta && action.meta.panesMap) {
|
|
|
|
const panesMap = action.meta.panesMap;
|
|
|
|
const panes = _.map(panesMap, (name, type) => ({ name, type }));
|
2017-11-09 17:10:30 -08:00
|
|
|
const numOfPanes = Object.keys(panes).length;
|
|
|
|
return {
|
|
|
|
...state,
|
2017-11-29 15:44:51 -08:00
|
|
|
panesMap,
|
2017-11-09 17:10:30 -08:00
|
|
|
panes,
|
|
|
|
panesByName: panes.reduce((panes, { name }, index) => {
|
|
|
|
const dividerLeft = getDividerLeft(numOfPanes, index);
|
|
|
|
panes[name] = {
|
|
|
|
name,
|
|
|
|
dividerLeft,
|
2017-12-29 12:29:03 +00:00
|
|
|
isHidden: name === 'Map' ? state.isMapPaneHidden : false
|
2017-11-09 17:10:30 -08:00
|
|
|
};
|
|
|
|
return panes;
|
|
|
|
}, {})
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (action.meta && action.meta.isPaneAction) {
|
|
|
|
const name = action.meta.paneName;
|
|
|
|
const oldPane = state.panesByName[name];
|
|
|
|
const pane = {
|
|
|
|
...oldPane,
|
|
|
|
isHidden: !oldPane.isHidden
|
|
|
|
};
|
|
|
|
const panesByName = {
|
|
|
|
...state.panesByName,
|
|
|
|
[name]: pane
|
|
|
|
};
|
|
|
|
const numOfPanes = state.panes.reduce((sum, { name }) => {
|
|
|
|
return panesByName[name].isHidden ? sum : sum + 1;
|
|
|
|
}, 0);
|
|
|
|
let numOfHidden = 0;
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
panesByName: state.panes.reduce(
|
|
|
|
(panesByName, { name }, index) => {
|
|
|
|
if (!panesByName[name].isHidden) {
|
|
|
|
const dividerLeft = getDividerLeft(
|
|
|
|
numOfPanes,
|
|
|
|
index - numOfHidden
|
|
|
|
);
|
|
|
|
panesByName[name] = {
|
|
|
|
...panesByName[name],
|
|
|
|
dividerLeft
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
numOfHidden = numOfHidden + 1;
|
|
|
|
}
|
|
|
|
return panesByName;
|
|
|
|
},
|
|
|
|
panesByName
|
|
|
|
)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return state;
|
2017-07-31 20:04:01 -07:00
|
|
|
}
|
2017-11-09 17:10:30 -08:00
|
|
|
);
|
2017-07-31 20:04:01 -07:00
|
|
|
|
|
|
|
return {
|
2017-11-09 17:10:30 -08:00
|
|
|
reducer,
|
2017-07-31 20:04:01 -07:00
|
|
|
middleware
|
|
|
|
};
|
|
|
|
}
|