diff --git a/__tests__/src/actions/companionWindow.test.js b/__tests__/src/actions/companionWindow.test.js index 8e41fa4718bc8320271440395764a153a680141d..fd28ad7cd4482b1d8cb511f80e699de1fd1f18f7 100644 --- a/__tests__/src/actions/companionWindow.test.js +++ b/__tests__/src/actions/companionWindow.test.js @@ -3,45 +3,51 @@ import ActionTypes from '../../../src/state/actions/action-types'; describe('companionWindow actions', () => { describe('addCompanionWindow', () => { - it('should create a new companion window with the given options', () => { - const options = { - id: 'abc123', - windowId: 'x', + it('should return correct action object', () => { + const payload = { content: 'info', position: 'right', + foo: 'bar', }; + const action = actions.addCompanionWindow(payload); + expect(action.type).toBe(ActionTypes.ADD_COMPANION_WINDOW); + expect(action.payload).toEqual(payload); + }); - const expectedAction = { - type: ActionTypes.SET_COMPANION_WINDOW, - id: 'abc123', - windowId: 'x', - content: 'info', - position: 'right', - }; - expect(actions.addCompanionWindow(options)).toEqual(expectedAction); + it('should set the correct default values', () => { + const payload = {}; + const defaults = { foo: 'bar' }; + const action = actions.addCompanionWindow(payload, defaults); + expect(action.payload.foo).toBe('bar'); }); - it('should generate a new companionWindow ID if one is not provided', () => { - const options = { - windowId: 'x', - content: 'info', - position: 'right', - }; + it('should generate a new companionWindow ID', () => { + const payload = {}; - expect(actions.addCompanionWindow(options).id).toEqual( + expect(actions.addCompanionWindow(payload).id).toEqual( expect.stringMatching(/^cw-\w+-\w+/), ); }); }); - describe('removeCompanionWindow', () => { - it('should send the REMOVE_COMPANION_WINDOW action with the given ID', () => { - const expectedAction = { - type: ActionTypes.REMOVE_COMPANION_WINDOW, - id: 'abc123', + describe('updateCompanionWindow', () => { + it('should return correct action object', () => { + const payload = { + content: 'info', + position: 'right', }; + const action = actions.updateCompanionWindow('cw-123', payload); + expect(action.type).toBe(ActionTypes.UPDATE_COMPANION_WINDOW); + expect(action.id).toBe('cw-123'); + expect(action.payload).toEqual(payload); + }); + }); - expect(actions.removeCompanionWindow('abc123')).toEqual(expectedAction); + describe('removeCompanionWindow', () => { + it('should return correct action object', () => { + const action = actions.removeCompanionWindow('cw-123'); + expect(action.type).toBe(ActionTypes.REMOVE_COMPANION_WINDOW); + expect(action.id).toBe('cw-123'); }); }); }); diff --git a/__tests__/src/actions/window.test.js b/__tests__/src/actions/window.test.js index ec76a792363165102aa1a2fce38b51726a430ba2..2dde5c7c11ba78e4f83b9e9cc8e93b369f3dbf30 100644 --- a/__tests__/src/actions/window.test.js +++ b/__tests__/src/actions/window.test.js @@ -27,6 +27,20 @@ describe('window actions', () => { expect(actions.addWindow(options)).toEqual(expectedAction); }); }); + + describe('updateWindow', () => { + it('should return correct action object', () => { + const payload = { + foo: 1, + bar: 2, + }; + const action = actions.updateWindow('window-123', payload); + expect(action.type).toBe(ActionTypes.UPDATE_WINDOW); + expect(action.id).toBe('window-123'); + expect(action.payload).toEqual(payload); + }); + }); + describe('removeWindow', () => { it('removes the window and returns windowId', () => { const id = 'abc123'; @@ -88,20 +102,41 @@ describe('window actions', () => { describe('popOutCompanionWindow', () => { it('returns a thunk which dispatches the appropriate actions', () => { - const mockDispatch = jest.fn(); + const mockState = { + windows: { + abc123: { + companionWindowIds: ['cw-1'], + }, + }, + }; + const mockDispatch = jest.fn(() => ({ id: 'cw-1' })); + const mockGetState = jest.fn(() => mockState); const windowId = 'abc123'; const panelType = 'info'; const position = 'right'; const thunk = actions.popOutCompanionWindow(windowId, panelType, position); expect(typeof thunk).toEqual('function'); - thunk(mockDispatch); - expect(mockDispatch).toHaveBeenCalledTimes(2); - const cwId = mockDispatch.mock.calls[0][0].id; - expect(mockDispatch).toHaveBeenCalledWith({ - type: ActionTypes.SET_COMPANION_WINDOW, id: cwId, windowId, content: panelType, position, + thunk(mockDispatch, mockGetState); + expect(mockDispatch).toHaveBeenCalledTimes(4); + + expect(mockDispatch).toHaveBeenNthCalledWith(1, { + type: ActionTypes.REMOVE_COMPANION_WINDOW, + id: 'cw-1', + }); + + const addCompanionWindowAction = mockDispatch.mock.calls[1][0]; + expect(addCompanionWindowAction.type).toBe(ActionTypes.ADD_COMPANION_WINDOW); + expect(addCompanionWindowAction.payload).toEqual({ content: 'info', position: 'right' }); + expect(addCompanionWindowAction.id.startsWith('cw-')).toBe(true); + + expect(mockDispatch).toHaveBeenNthCalledWith(3, { + type: ActionTypes.UPDATE_WINDOW, + id: 'abc123', + payload: { companionWindowIds: ['cw-1'] }, }); - expect(mockDispatch).toHaveBeenCalledWith({ + + expect(mockDispatch).toHaveBeenNthCalledWith(4, { type: ActionTypes.TOGGLE_WINDOW_SIDE_BAR_PANEL, windowId, panelType: 'closed', }); }); diff --git a/__tests__/src/components/CompanionWindow.test.js b/__tests__/src/components/CompanionWindow.test.js index 68cc647cf31d2c8973ab1421c402dd477ff9e967..f155abd8ef11cdfcad8ea38296e1f6fee67137bb 100644 --- a/__tests__/src/components/CompanionWindow.test.js +++ b/__tests__/src/components/CompanionWindow.test.js @@ -35,7 +35,7 @@ describe('CompanionWindow', () => { }); describe('when the close companion window button is clicked', () => { - it('triggers the removeCompanionWindow prop with the appropriate args', () => { + it('triggers the onCloseClick prop with the appropriate args', () => { const removeCompanionWindowEvent = jest.fn(); companionWindow = createWrapper({ onCloseClick: removeCompanionWindowEvent, @@ -44,7 +44,7 @@ describe('CompanionWindow', () => { const closeButton = companionWindow.find('WithStyles(IconButton)[aria-label="closeCompanionWindow"]'); closeButton.simulate('click'); expect(removeCompanionWindowEvent).toHaveBeenCalledTimes(1); - expect(removeCompanionWindowEvent).toHaveBeenCalledWith('abc123', 'x'); + expect(removeCompanionWindowEvent).toHaveBeenCalledWith('x', 'abc123'); }); }); }); diff --git a/__tests__/src/components/WindowMiddleContent.test.js b/__tests__/src/components/WindowMiddleContent.test.js index 9088c569704174fd8706a2aa21b71401587a2678..64da2c84b34af25fa14cb51b23809348ef418e74 100644 --- a/__tests__/src/components/WindowMiddleContent.test.js +++ b/__tests__/src/components/WindowMiddleContent.test.js @@ -5,24 +5,34 @@ import CompanionWindow from '../../../src/containers/CompanionWindow'; import WindowSideBar from '../../../src/containers/WindowSideBar'; import WindowViewer from '../../../src/containers/WindowViewer'; +/** create wrapper */ +function createWrapper(props) { + return shallow( + <WindowMiddleContent + companionWindowIds={['cw1', 'cw-2']} + window={{ id: 'window-1' }} + manifest={{}} + {...props} + />, + ); +} + describe('WindowMiddleContent', () => { - let wrapper; - let manifest; it('should render outer element', () => { - wrapper = shallow(<WindowMiddleContent window={window} />); + const wrapper = createWrapper(); expect(wrapper.find('.mirador-window-middle-content')).toHaveLength(1); }); - it('should render <CompanionWindow>', () => { - wrapper = shallow(<WindowMiddleContent window={window} rightCompanionWindowId="x" />); - expect(wrapper.find(CompanionWindow)).toHaveLength(1); + it('should render all <CompanionWindow> components', () => { + const wrapper = createWrapper(); + expect(wrapper.find(CompanionWindow)).toHaveLength(2); }); it('should render <WindowSideBar>', () => { - wrapper = shallow(<WindowMiddleContent window={window} />); + const wrapper = createWrapper(); expect(wrapper.find(WindowSideBar)).toHaveLength(1); }); it('should render <WindowViewer> if manifest is present', () => { - manifest = { id: 456, isFetching: false }; - wrapper = shallow(<WindowMiddleContent window={window} manifest={manifest} />); + const manifest = { id: 456, isFetching: false }; + const wrapper = createWrapper({ manifest }); expect(wrapper.find(WindowViewer)).toHaveLength(1); }); }); diff --git a/__tests__/src/components/WindowTopBar.test.js b/__tests__/src/components/WindowTopBar.test.js index e482f635c53eeb19ff208026f008458e76c9ef3d..4fe237023b1c07600e28eb782d73d9eea2941d25 100644 --- a/__tests__/src/components/WindowTopBar.test.js +++ b/__tests__/src/components/WindowTopBar.test.js @@ -20,7 +20,7 @@ function createWrapper(props) { windowId="xyz" classes={{}} t={str => str} - removeWindow={() => {}} + closeWindow={() => {}} toggleWindowSideBar={() => {}} {...props} />, @@ -67,8 +67,8 @@ describe('WindowTopBar', () => { }); it('passes correct props to <Button/>', () => { - const removeWindow = jest.fn(); - const wrapper = createWrapper({ removeWindow }); - expect(wrapper.find(IconButton).last().props().onClick).toBe(removeWindow); + const closeWindow = jest.fn(); + const wrapper = createWrapper({ closeWindow }); + expect(wrapper.find(IconButton).last().props().onClick).toBe(closeWindow); }); }); diff --git a/__tests__/src/reducers/companionWindows.test.js b/__tests__/src/reducers/companionWindows.test.js new file mode 100644 index 0000000000000000000000000000000000000000..68775833910068ef0252fdf95084980790c37c1a --- /dev/null +++ b/__tests__/src/reducers/companionWindows.test.js @@ -0,0 +1,63 @@ +import { companionWindowsReducer } from '../../../src/state/reducers/companionWindows'; +import ActionTypes from '../../../src/state/actions/action-types'; + +describe('companionWindowsReducer', () => { + describe('ADD_COMPANION_WINDOW', () => { + it('adds a new companion window', () => { + const action = { + type: ActionTypes.ADD_COMPANION_WINDOW, + id: 'abc123', + payload: { content: 'info', position: 'right' }, + }; + const beforeState = {}; + const expectedState = { + abc123: { + position: 'right', + content: 'info', + }, + }; + expect(companionWindowsReducer(beforeState, action)).toEqual(expectedState); + }); + }); + + describe('UPDATE_COMPANION_WINDOW', () => { + it('updates an existing companion window', () => { + const action = { + type: ActionTypes.UPDATE_COMPANION_WINDOW, + id: 'abc123', + payload: { content: 'canvases', foo: 'bar' }, + }; + const beforeState = { + abc123: { + position: 'right', + content: 'info', + }, + }; + const expectedState = { + abc123: { + position: 'right', + content: 'canvases', + foo: 'bar', + }, + }; + expect(companionWindowsReducer(beforeState, action)).toEqual(expectedState); + }); + }); + + describe('REMOVE_COMPANION_WINDOW', () => { + it('should remove a companion window', () => { + const action = { + type: ActionTypes.REMOVE_COMPANION_WINDOW, + id: 'abc123', + }; + const beforeState = { + abc123: { + position: 'right', + content: 'info', + }, + }; + const expectedState = {}; + expect(companionWindowsReducer(beforeState, action)).toEqual(expectedState); + }); + }); +}); diff --git a/__tests__/src/reducers/companion_windows.test.js b/__tests__/src/reducers/companion_windows.test.js deleted file mode 100644 index ab0d0c5f465e357d86de385ad7c7bd2ad1dbff0c..0000000000000000000000000000000000000000 --- a/__tests__/src/reducers/companion_windows.test.js +++ /dev/null @@ -1,75 +0,0 @@ -import { companionWindowsReducer } from '../../../src/state/reducers/companion_windows'; -import ActionTypes from '../../../src/state/actions/action-types'; - -describe('companionWindowsReducer', () => { - describe('SET_COMPANION_WINDOW', () => { - it('adds a new companion window if a companion window for the given position does not exist', () => { - const action = { - type: ActionTypes.SET_COMPANION_WINDOW, - id: 'abc123', - windowId: 'x', - position: 'right', - content: 'info', - }; - const beforeState = {}; - const expectedState = { - abc123: { - id: 'abc123', windowId: 'x', position: 'right', content: 'info', - }, - }; - - expect(companionWindowsReducer(beforeState, action)).toEqual(expectedState); - }); - - it('updates an existing companion window based on windowId and position (regardless of companionWindowId)', () => { - const action = { - type: ActionTypes.SET_COMPANION_WINDOW, - id: 'xyz321', - windowId: 'x', - position: 'right', - content: 'info', - }; - const beforeState = { - abc123: { - id: 'abc123', windowId: 'x', position: 'right', content: 'canvas_navigation', - }, - }; - const expectedState = { - abc123: { - id: 'abc123', windowId: 'x', position: 'right', content: 'info', - }, - }; - - expect(companionWindowsReducer(beforeState, action)).toEqual(expectedState); - }); - }); - - describe('REMOVE_COMPANION_WINDOW', () => { - it('removes the companion window w/ the given ID', () => { - const action = { type: ActionTypes.REMOVE_COMPANION_WINDOW, id: 'abc123' }; - const beforeState = { abc123: { id: 'abc123' } }; - const expectedState = {}; - - expect(companionWindowsReducer(beforeState, action)).toEqual(expectedState); - }); - }); - - describe('REMOVE_WINDOW', () => { - it('removes any companion window that has the given windowId', () => { - const action = { - type: ActionTypes.REMOVE_WINDOW, - windowId: 'x', - }; - const beforeState = { - abc123: { windowId: 'x' }, - abc456: { windowId: 'y' }, - abc789: { windowId: 'x' }, - }; - const expectedState = { - abc456: { windowId: 'y' }, - }; - - expect(companionWindowsReducer(beforeState, action)).toEqual(expectedState); - }); - }); -}); diff --git a/__tests__/src/reducers/windows.test.js b/__tests__/src/reducers/windows.test.js index 7402360aa7cfdb5a16be3998f4edf9f0fb4fc908..e8beeabbbf756d5feda29645d26af0751986b2a8 100644 --- a/__tests__/src/reducers/windows.test.js +++ b/__tests__/src/reducers/windows.test.js @@ -121,61 +121,6 @@ describe('windows reducer', () => { }); }); - describe('SET_COMPANION_WINDOW', () => { - it('adds the id to the companin array', () => { - const action = { - type: ActionTypes.SET_COMPANION_WINDOW, - id: 'x', - windowId: 'abc123', - }; - const before = { - abc123: { companionWindowIds: [] }, - }; - const after = { - abc123: { companionWindowIds: ['x'] }, - }; - - expect(windowsReducer(before, action)).toEqual(after); - }); - - it('does not add id key that already exists', () => { - const action = { - type: ActionTypes.SET_COMPANION_WINDOW, - id: 'x', - windowId: 'abc123', - position: 'right', - panelType: 'info', - }; - const before = { - abc123: { companionWindowIds: ['x'] }, - }; - - const after = { - abc123: { companionWindowIds: ['x'] }, - }; - - expect(windowsReducer(before, action)).toEqual(after); - }); - }); - - describe('REMOVE_COMPANION_WINDOW', () => { - it('removes the id of the companionWindow from the ids array', () => { - const action = { - type: ActionTypes.REMOVE_COMPANION_WINDOW, - id: 'x', - windowId: 'abc123', - }; - const before = { - abc123: { companionWindowIds: ['x'] }, - }; - const after = { - abc123: { companionWindowIds: [] }, - }; - - expect(windowsReducer(before, action)).toEqual(after); - }); - }); - it('should handle NEXT_CANVAS', () => { expect(windowsReducer({ abc123: { @@ -249,4 +194,28 @@ describe('windows reducer', () => { }, }); }); + + describe('UPDATE_WINDOW', () => { + it('updates an existing window', () => { + const action = { + type: ActionTypes.UPDATE_WINDOW, + id: 'abc123', + payload: { foo: 11, baz: 33 }, + }; + const beforeState = { + abc123: { + foo: 1, + bar: 2, + }, + }; + const expectedState = { + abc123: { + foo: 11, + bar: 2, + baz: 33, + }, + }; + expect(windowsReducer(beforeState, action)).toEqual(expectedState); + }); + }); }); diff --git a/package.json b/package.json index 3378f26d956f2ebc84d388b8874de98a545c6ee0..156228a973f2ae817a969b06a9d7f2224c32a88c 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "deepmerge": "^3.1.0", "dompurify": "^1.0.9", "i18next": "^14.0.1", + "immutable": "^4.0.0-rc.12", "intersection-observer": "^0.5.1", "lodash": "^4.17.11", "manifesto.js": "^3.0.9", diff --git a/src/components/CompanionWindow.js b/src/components/CompanionWindow.js index 18c104fee0e090fa0fe5f0d2d359e5b0c7f6a582..b26f2f84ce1b900d64c531b9dca218a779650b55 100644 --- a/src/components/CompanionWindow.js +++ b/src/components/CompanionWindow.js @@ -48,7 +48,7 @@ class CompanionWindow extends Component { <IconButton aria-label={t('closeCompanionWindow')} className={classes.closeButton} - onClick={() => { onCloseClick(id, windowId); }} + onClick={() => { onCloseClick(windowId, id); }} > <CloseIcon /> </IconButton> diff --git a/src/components/WindowMiddleContent.js b/src/components/WindowMiddleContent.js index ce86d5644d5a7851154cdfb8ea8b3a75f7a3fa67..b2817e6b68d508c5898c51b06e0fcde6ea68fc3d 100644 --- a/src/components/WindowMiddleContent.js +++ b/src/components/WindowMiddleContent.js @@ -32,29 +32,24 @@ class WindowMiddleContent extends Component { * Render the component */ render() { - const { rightCompanionWindowId, window } = this.props; + const { companionWindowIds, window } = this.props; return ( <div className={ns('window-middle-content')}> <WindowSideBar windowId={window.id} /> {this.renderViewer()} - { // We can pass an array of ids here when we want to support - // multiple companion windows in a particular position - rightCompanionWindowId - && <CompanionWindow id={rightCompanionWindowId} windowId={window.id} /> - } + { companionWindowIds.map(id => <CompanionWindow key={id} id={id} windowId={window.id} />) } </div> ); } } WindowMiddleContent.propTypes = { - rightCompanionWindowId: PropTypes.string, + companionWindowIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types window: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types manifest: PropTypes.object, // eslint-disable-line react/forbid-prop-types }; WindowMiddleContent.defaultProps = { - rightCompanionWindowId: null, manifest: null, }; diff --git a/src/components/WindowTopBar.js b/src/components/WindowTopBar.js index 7e5969cd85d2d4602eb03e57c90cba13db76d4a8..846fe95780dc0d91dd256a410a856a66bacc5f2c 100644 --- a/src/components/WindowTopBar.js +++ b/src/components/WindowTopBar.js @@ -24,7 +24,7 @@ class WindowTopBar extends Component { */ render() { const { - removeWindow, windowId, classes, toggleWindowSideBar, t, manifestTitle, + closeWindow, windowId, classes, toggleWindowSideBar, t, manifestTitle, } = this.props; return ( <AppBar position="relative"> @@ -46,7 +46,7 @@ class WindowTopBar extends Component { color="inherit" className={ns('window-close')} aria-label={t('closeWindow')} - onClick={removeWindow} + onClick={closeWindow} > <CloseIcon /> </IconButton> @@ -58,7 +58,7 @@ class WindowTopBar extends Component { WindowTopBar.propTypes = { manifestTitle: PropTypes.string, - removeWindow: PropTypes.func.isRequired, + closeWindow: PropTypes.func.isRequired, windowId: PropTypes.string.isRequired, classes: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types toggleWindowSideBar: PropTypes.func.isRequired, diff --git a/src/containers/CompanionWindow.js b/src/containers/CompanionWindow.js index c5801ba9ab1b2390206bbe5651eaa967028d8502..0572435bf5a9c4fd7bc012dcdf47cacbc4b96d60 100644 --- a/src/containers/CompanionWindow.js +++ b/src/containers/CompanionWindow.js @@ -27,7 +27,7 @@ const mapStateToProps = (state, { id }) => { * @private */ const mapDispatchToProps = { - onCloseClick: actions.removeCompanionWindow, + onCloseClick: actions.closeCompanionWindow, }; const enhance = compose( diff --git a/src/containers/WindowMiddleContent.js b/src/containers/WindowMiddleContent.js index 81630c71069f99014e6141f58982fb47e81bffa1..9ca434593329cc514e680d4a05b0be79b0fc5860 100644 --- a/src/containers/WindowMiddleContent.js +++ b/src/containers/WindowMiddleContent.js @@ -1,17 +1,13 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; -import { getCompanionWindowForPosition } from '../state/selectors'; +import { getCompantionWindowIds } from '../state/selectors'; import miradorWithPlugins from '../lib/miradorWithPlugins'; import WindowMiddleContent from '../components/WindowMiddleContent'; /** */ -const mapStateToProps = (state, { window }) => { - const rightCompanionWindow = getCompanionWindowForPosition(state, window.id, 'right'); - - return { - rightCompanionWindowId: rightCompanionWindow && rightCompanionWindow.id, - }; -}; +const mapStateToProps = (state, { window }) => ({ + companionWindowIds: getCompantionWindowIds(state, window.id), +}); const enhance = compose( connect(mapStateToProps, null), diff --git a/src/containers/WindowTopBar.js b/src/containers/WindowTopBar.js index 1d193b101f5f425ea21d5de7a37248d3509ab0b4..191ebb59ca3256d1f782028fc4f3583a9c30f3fb 100644 --- a/src/containers/WindowTopBar.js +++ b/src/containers/WindowTopBar.js @@ -17,7 +17,7 @@ const mapStateToProps = (state, { windowId }) => ({ * @private */ const mapDispatchToProps = (dispatch, { windowId }) => ({ - removeWindow: () => dispatch(actions.removeWindow(windowId)), + closeWindow: () => dispatch(actions.closeWindow(windowId)), toggleWindowSideBar: () => dispatch(actions.toggleWindowSideBar(windowId)), }); diff --git a/src/state/actions/action-types.js b/src/state/actions/action-types.js index 79c13ef4168f36a45c33575e159887885ce680c1..117eda9052691186eea6ead2d20257607cc8c702 100644 --- a/src/state/actions/action-types.js +++ b/src/state/actions/action-types.js @@ -1,6 +1,9 @@ const ActionTypes = { - SET_COMPANION_WINDOW: 'SET_COMPANION_WINDOW', + ADD_COMPANION_WINDOW: 'ADD_COMPANION_WINDOW', + UPDATE_COMPANION_WINDOW: 'UPDATE_COMPANION_WINDOW', REMOVE_COMPANION_WINDOW: 'REMOVE_COMPANION_WINDOW', + UPDATE_WINDOW: 'UPDATE_WINDOW', + FOCUS_WINDOW: 'FOCUS_WINDOW', SET_WORKSPACE_FULLSCREEN: 'SET_WORKSPACE_FULLSCREEN', ADD_MANIFEST: 'ADD_MANIFEST', diff --git a/src/state/actions/companionWindow.js b/src/state/actions/companionWindow.js index d86ed66d8a72890e644720a779d79fa0381d8010..43b27cab8db26afe77da928cf4f6b659c32cf2eb 100644 --- a/src/state/actions/companionWindow.js +++ b/src/state/actions/companionWindow.js @@ -1,30 +1,26 @@ import uuid from 'uuid/v4'; import ActionTypes from './action-types'; -/** - * addCompanionWindow - action creator - * - * @param {Object} options - * @memberof ActionCreators - */ -export function addCompanionWindow(companionWindow) { +const defaultProps = { + content: null, + position: null, +}; + +/** */ +export function addCompanionWindow(payload, defaults = defaultProps) { return { - type: ActionTypes.SET_COMPANION_WINDOW, + type: ActionTypes.ADD_COMPANION_WINDOW, id: `cw-${uuid()}`, - ...companionWindow, + payload: { ...defaults, ...payload }, }; } -/** - * removeCompanionWindow - action creator - * - * @param {Object} options - * @memberof ActionCreators - */ -export function removeCompanionWindow(id, windowId) { - return { - type: ActionTypes.REMOVE_COMPANION_WINDOW, - id, - windowId, - }; +/** */ +export function updateCompanionWindow(id, payload) { + return { type: ActionTypes.UPDATE_COMPANION_WINDOW, id, payload }; +} + +/** */ +export function removeCompanionWindow(id) { + return { type: ActionTypes.REMOVE_COMPANION_WINDOW, id }; } diff --git a/src/state/actions/window.js b/src/state/actions/window.js index 86d35bbdabed47d3e92f32129934e0ea039d0d4c..afb5c766f61eaea98e0ec3b6c4edca3861826e80 100644 --- a/src/state/actions/window.js +++ b/src/state/actions/window.js @@ -1,6 +1,6 @@ import uuid from 'uuid/v4'; import ActionTypes from './action-types'; -import { addCompanionWindow } from './companionWindow'; +import { addCompanionWindow, removeCompanionWindow } from './companionWindow'; /** * focusWindow - action creator @@ -34,6 +34,11 @@ export function addWindow(options) { return { type: ActionTypes.ADD_WINDOW, window: { ...defaultOptions, ...options } }; } +/** */ +export function updateWindow(id, payload) { + return { type: ActionTypes.UPDATE_WINDOW, id, payload }; +} + /** * removeWindow - action creator * @@ -76,10 +81,40 @@ export function toggleWindowSideBarPanel(windowId, panelType) { * @memberof ActionCreators */ export function popOutCompanionWindow(windowId, panelType, position) { - return ((dispatch) => { - dispatch(addCompanionWindow({ windowId, content: panelType, position })); + return (dispatch, getState) => { + const { companionWindowIds } = getState().windows[windowId]; + companionWindowIds.map(id => dispatch(removeCompanionWindow(id))); + + const action = dispatch(addCompanionWindow({ content: panelType, position })); + + const companionWindowId = action.id; + dispatch(updateWindow(windowId, { companionWindowIds: [companionWindowId] })); + dispatch(toggleWindowSideBarPanel(windowId, 'closed')); - }); + }; +} + +/** +* Clean up state and remove window +*/ +export function closeWindow(windowId) { + return (dispatch, getState) => { + const { companionWindowIds } = getState().windows[windowId]; + companionWindowIds.map(id => dispatch(removeCompanionWindow(id))); + dispatch(removeWindow(windowId)); + }; +} + +/** +* Close companion window and remove reference from window +*/ +export function closeCompanionWindow(windowId, companionWindowId) { + return (dispatch, getState) => { + dispatch(removeCompanionWindow(companionWindowId)); + const companionWindowIds = getState().windows[windowId].companionWindowIds + .filter(id => id !== companionWindowId); + dispatch(updateWindow(windowId, { companionWindowIds })); + }; } /** diff --git a/src/state/reducers/companionWindows.js b/src/state/reducers/companionWindows.js new file mode 100644 index 0000000000000000000000000000000000000000..c5cfc2dcc685b5570d1563974c423124591fcaf9 --- /dev/null +++ b/src/state/reducers/companionWindows.js @@ -0,0 +1,21 @@ +import { + removeIn, setIn, updateIn, merge, +} from 'immutable'; +import ActionTypes from '../actions/action-types'; + +/** */ +export function companionWindowsReducer(state = {}, action) { + switch (action.type) { + case ActionTypes.ADD_COMPANION_WINDOW: + return setIn(state, [action.id], action.payload); + + case ActionTypes.UPDATE_COMPANION_WINDOW: + return updateIn(state, [action.id], orig => merge(orig, action.payload)); + + case ActionTypes.REMOVE_COMPANION_WINDOW: + return removeIn(state, [action.id]); + + default: + return state; + } +} diff --git a/src/state/reducers/companion_windows.js b/src/state/reducers/companion_windows.js deleted file mode 100644 index d703a2352c2665325cc8248bd9ba3d8d68ae3cb5..0000000000000000000000000000000000000000 --- a/src/state/reducers/companion_windows.js +++ /dev/null @@ -1,54 +0,0 @@ -import ActionTypes from '../actions/action-types'; -import { getCompanionWindowForPosition } from '../selectors'; - -/** - * companionWindowsReducer - */ -export const companionWindowsReducer = (state = {}, action) => { - switch (action.type) { - // action params: id - case ActionTypes.REMOVE_COMPANION_WINDOW: - return Object.keys(state).reduce((object, key) => { - if (state[key].id !== action.id) { - object[key] = state[key]; // eslint-disable-line no-param-reassign - } - return object; - }, {}); - // action params: id, windowId, position, content - case ActionTypes.SET_COMPANION_WINDOW: { - const companionWindowForPosition = getCompanionWindowForPosition( - { companionWindows: state }, action.windowId, action.position, - ); - let cwId; - let cwObject; - - if (companionWindowForPosition) { - cwId = companionWindowForPosition.id; - cwObject = { - ...companionWindowForPosition, - position: action.position, - content: action.content, - }; - } else { - cwId = action.id; - cwObject = { - id: cwId, windowId: action.windowId, position: action.position, content: action.content, - }; - } - - return { ...state, [cwId]: cwObject }; - } - - - // action params: windowId - case ActionTypes.REMOVE_WINDOW: - return Object.keys(state).reduce((object, key) => { - if (state[key].windowId !== action.windowId) { - object[key] = state[key]; // eslint-disable-line no-param-reassign - } - return object; - }, {}); - default: - return state; - } -}; diff --git a/src/state/reducers/index.js b/src/state/reducers/index.js index 626131733a5407542f206249d2d963946dab76fc..478ac4467e511e258bce3edd51967b9d7496cc10 100644 --- a/src/state/reducers/index.js +++ b/src/state/reducers/index.js @@ -1,4 +1,4 @@ -export * from './companion_windows'; +export * from './companionWindows'; export * from './workspace'; export * from './windows'; export * from './manifests'; diff --git a/src/state/reducers/windows.js b/src/state/reducers/windows.js index 5530e897ee3ee41bc3f53718d1f92a0c597f8843..7a1739f8e481d9380bd89d709ea843be247a481c 100644 --- a/src/state/reducers/windows.js +++ b/src/state/reducers/windows.js @@ -1,3 +1,4 @@ +import { updateIn, merge } from 'immutable'; import ActionTypes from '../actions/action-types'; /** @@ -7,6 +8,10 @@ export const windowsReducer = (state = {}, action) => { switch (action.type) { case ActionTypes.ADD_WINDOW: return { ...state, [action.window.id]: action.window }; + + case ActionTypes.UPDATE_WINDOW: + return updateIn(state, [action.id], orig => merge(orig, action.payload)); + case ActionTypes.REMOVE_WINDOW: return Object.keys(state).reduce((object, key) => { if (key !== action.windowId) { @@ -50,26 +55,6 @@ export const windowsReducer = (state = {}, action) => { ), }, }; - case ActionTypes.SET_COMPANION_WINDOW: { - return { - ...state, - [action.windowId]: { - ...state[action.windowId], - companionWindowIds: Array.from( - new Set([].concat(state[action.windowId].companionWindowIds, action.id)), - ), - }, - }; - } - case ActionTypes.REMOVE_COMPANION_WINDOW: - return { - ...state, - [action.windowId]: { - ...state[action.windowId], - companionWindowIds: state[action.windowId] - .companionWindowIds.filter(id => id !== action.id), - }, - }; case ActionTypes.NEXT_CANVAS: return setCanvasIndex(state, action.windowId, currentIndex => currentIndex + 1); case ActionTypes.PREVIOUS_CANVAS: diff --git a/src/state/selectors/index.js b/src/state/selectors/index.js index 1b3333583b782fe1628a6dc267e19c19f463eca6..e7b4565c634581f94ddc3ff0caf2e9f251c2dacd 100644 --- a/src/state/selectors/index.js +++ b/src/state/selectors/index.js @@ -192,3 +192,12 @@ export function getCompanionWindowForPosition(state, windowId, position) { cw.windowId === windowId && cw.position === position )); } + +/** +* Return compantion window ids from a window +* @param {String} windowId +* @return {Array} +*/ +export function getCompantionWindowIds(state, windowId) { + return state.windows[windowId].companionWindowIds; +}