import _ from 'lodash'; import { composeReducers, createAction, createTypes, handleActions } from 'berkeleys-redux-utils'; import * as utils from './utils.js'; import windowEpic from './window-epic.js'; import dividerEpic from './divider-epic.js'; import ns from '../ns.json'; import { types as challengeTypes } from '../../routes/Challenges/redux'; export const epics = [ windowEpic, dividerEpic ]; export const types = createTypes([ 'panesMapUpdated', 'panesMounted', 'panesUpdated', 'panesWillMount', 'panesWillUnmount', 'updateSize', 'dividerClicked', 'dividerMoved', 'mouseReleased', 'windowResized', // commands 'hidePane', 'updateNavHeight' ], ns); export const panesMapUpdated = createAction( types.panesMapUpdated, null, (type, panesMap) => ({ trigger: type, panesMap }) ); 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); export const updateNavHeight = createAction(types.updateNavHeight); const defaultState = { height: 600, width: 800, navHeight: 50, isMapPaneHidden: false, panes: [], panesByName: {}, pressedDivider: null, panesMap: {} }; 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; export const panesMapSelector = state => getNS(state).panesMap; export default function createPanesAspects({ createPanesMap }) { createPanesMap = utils.normalizePanesMapCreator(createPanesMap); function middleware({ getState }) { return next => action => { let finalAction = action; const panesMap = panesMapSelector(getState()); if (utils.isPanesAction(action, panesMap)) { finalAction = { ...action, meta: { ...action.meta, isPaneAction: true, paneName: panesMap[action.type] } }; } const result = next(finalAction); const nextPanesMap = createPanesMap(getState(), action); if (nextPanesMap) { utils.checkForTypeKeys(nextPanesMap); next(panesMapUpdated(action.type, nextPanesMap)); } return result; }; } const reducer = composeReducers( ns, handleActions( () => ({ [types.dividerClicked]: (state, { payload: name }) => ({ ...state, pressedDivider: name }), [types.dividerMoved]: (state, { payload: clientX }) => { const { panes, panesByName, pressedDivider: paneName, width } = state; const dividerBuffer = (200 / width) * 100; const paneIndex = _.findIndex(state.panes, ({ name }) => paneName === name); const currentPane = panesByName[paneName]; const rightPane = utils.getPane(panesByName, panes, paneIndex + 1); const leftPane = utils.getPane(panesByName, panes, paneIndex - 1); const rightBound = utils.getRightBound(rightPane, dividerBuffer); const leftBound = utils.getLeftBound(leftPane, dividerBuffer); const newPosition = _.clamp( (clientX / width) * 100, leftBound, rightBound ); return { ...state, panesByName: { ...state.panesByName, [currentPane.name]: { ...currentPane, dividerLeft: newPosition } } }; }, [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 }), [challengeTypes.toggleMap]: state => ({ ...state, isMapPaneHidden: !state.isMapPaneHidden }) }), defaultState ), function metaReducer(state = defaultState, action) { if (action.meta && action.meta.panesMap) { const panesMap = action.meta.panesMap; const panes = _.map(panesMap, (name, type) => ({ name, type })); const numOfPanes = Object.keys(panes).length; return { ...state, panesMap, panes, panesByName: panes.reduce((panes, { name }, index) => { const dividerLeft = utils.getDividerLeft(numOfPanes, index); panes[name] = { name, dividerLeft, isHidden: name === 'Map' ? state.isMapPaneHidden : false }; 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 = utils.getDividerLeft( numOfPanes, index - numOfHidden ); panesByName[name] = { ...panesByName[name], dividerLeft }; } else { numOfHidden = numOfHidden + 1; } return panesByName; }, panesByName ) }; } return state; } ); return { reducer, middleware }; }