diff --git a/__tests__/src/components/Workspace.test.js b/__tests__/src/components/Workspace.test.js index fde08305b75da6c5a37e8f2edd75ee3c2e043180..138523c2b169aa9f09efe8d0b523a58622f37368 100644 --- a/__tests__/src/components/Workspace.test.js +++ b/__tests__/src/components/Workspace.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { Mosaic } from 'react-mosaic-component'; import Workspace from '../../../src/components/Workspace'; +import Window from '../../../src/containers/Window'; describe('Workspace', () => { const windows = { 1: { id: 1 }, 2: { id: 2 } }; @@ -10,62 +10,31 @@ describe('Workspace', () => { wrapper = shallow( <Workspace windows={windows} - workspace={{}} - updateWorkspaceMosaicLayout={() => {}} + config={{ workspace: { type: 'mosaic' } }} />, ); }); - it('should render properly with an initialValue', () => { - expect(wrapper.matchesElement( - <div className="mirador-workspace"> - <Mosaic initialValue={{ direction: 'row', first: '1', second: '2' }} /> - </div>, - )).toBe(true); + it('should render properly', () => { + expect(wrapper.find('.mirador-workspace').length).toBe(1); + expect(wrapper.find('Connect(WorkspaceMosaic)').length).toBe(1); }); - describe('determineWorkspaceLayout', () => { - it('when window ids do not match workspace layout', () => { - wrapper = shallow( - <Workspace - windows={windows} - workspace={{ layout: 'foo' }} - updateWorkspaceMosaicLayout={() => {}} - />, - ); - expect(wrapper.instance().determineWorkspaceLayout()).toMatchObject({ - direction: 'row', first: '1', second: '2', - }); + describe('workspaceByType', () => { + it('when mosaic', () => { + expect(wrapper.find('Connect(WorkspaceMosaic)').length).toBe(1); }); - it('when window ids match workspace layout', () => { + it('anything else', () => { wrapper = shallow( <Workspace - windows={{ foo: { id: 'foo' } }} - workspace={{ layout: 'foo' }} - updateWorkspaceMosaicLayout={() => {}} - />, - ); - expect(wrapper.instance().determineWorkspaceLayout()).toBeNull(); - }); - }); - describe('tileRenderer', () => { - it('when window is available', () => { - expect(wrapper.instance().tileRenderer('1')).not.toBeNull(); - }); - it('when window is not available', () => { - expect(wrapper.instance().tileRenderer('bar')).toBeNull(); - }); - }); - describe('mosaicChange', () => { - it('calls the provided prop to update layout', () => { - const mock = jest.fn(); - wrapper = shallow( - <Workspace - windows={{ foo: { id: 'foo' } }} - workspace={{ layout: 'foo' }} - updateWorkspaceMosaicLayout={mock} + windows={windows} + config={{ workspace: { type: 'foo' } }} />, ); - wrapper.instance().mosaicChange(); - expect(mock).toBeCalled(); + expect(wrapper.matchesElement( + <div className="mirador-workspace"> + <Window window={{ id: 1 }} /> + <Window window={{ id: 2 }} /> + </div>, + )).toBe(true); }); }); }); diff --git a/__tests__/src/components/WorkspaceMosaic.test.js b/__tests__/src/components/WorkspaceMosaic.test.js new file mode 100644 index 0000000000000000000000000000000000000000..64b2a013163298979de207e54edeeae2540525ea --- /dev/null +++ b/__tests__/src/components/WorkspaceMosaic.test.js @@ -0,0 +1,69 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { Mosaic } from 'react-mosaic-component'; +import WorkspaceMosaic from '../../../src/components/WorkspaceMosaic'; + +describe('WorkspaceMosaic', () => { + const windows = { 1: { id: 1 }, 2: { id: 2 } }; + let wrapper; + beforeEach(() => { + wrapper = shallow( + <WorkspaceMosaic + windows={windows} + workspace={{}} + updateWorkspaceMosaicLayout={() => {}} + />, + ); + }); + it('should render properly with an initialValue', () => { + expect(wrapper.matchesElement( + <Mosaic initialValue={{ direction: 'row', first: '1', second: '2' }} />, + )).toBe(true); + }); + describe('determineWorkspaceLayout', () => { + it('when window ids do not match workspace layout', () => { + wrapper = shallow( + <WorkspaceMosaic + windows={windows} + workspace={{ layout: 'foo' }} + updateWorkspaceMosaicLayout={() => {}} + />, + ); + expect(wrapper.instance().determineWorkspaceLayout()).toMatchObject({ + direction: 'row', first: '1', second: '2', + }); + }); + it('when window ids match workspace layout', () => { + wrapper = shallow( + <WorkspaceMosaic + windows={{ foo: { id: 'foo' } }} + workspace={{ layout: 'foo' }} + updateWorkspaceMosaicLayout={() => {}} + />, + ); + expect(wrapper.instance().determineWorkspaceLayout()).toBeNull(); + }); + }); + describe('tileRenderer', () => { + it('when window is available', () => { + expect(wrapper.instance().tileRenderer('1')).not.toBeNull(); + }); + it('when window is not available', () => { + expect(wrapper.instance().tileRenderer('bar')).toBeNull(); + }); + }); + describe('mosaicChange', () => { + it('calls the provided prop to update layout', () => { + const mock = jest.fn(); + wrapper = shallow( + <WorkspaceMosaic + windows={{ foo: { id: 'foo' } }} + workspace={{ layout: 'foo' }} + updateWorkspaceMosaicLayout={mock} + />, + ); + wrapper.instance().mosaicChange(); + expect(mock).toBeCalled(); + }); + }); +}); diff --git a/src/components/Workspace.js b/src/components/Workspace.js index de8694c11835ec14874a7d8c254b3717be9dfcbe..35cef899bf2e6092c1b19064a4a243cc30fa9f72 100644 --- a/src/components/Workspace.js +++ b/src/components/Workspace.js @@ -1,10 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Mosaic, getLeaves, createBalancedTreeFromLeaves, -} from 'react-mosaic-component'; -import 'react-mosaic-component/react-mosaic-component.css'; import Window from '../containers/Window'; +import WorkspaceMosaic from '../containers/WorkspaceMosaic'; import ns from '../config/css-ns'; /** @@ -18,77 +15,42 @@ class Workspace extends React.Component { constructor(props) { super(props); - this.tileRenderer = this.tileRenderer.bind(this); - this.mosaicChange = this.mosaicChange.bind(this); - this.determineWorkspaceLayout = this.determineWorkspaceLayout.bind(this); + this.workspaceByType = this.workspaceByType.bind(this); } /** - * Render a tile (Window) in the Mosaic. + * Determine which workspace to render by configured type */ - tileRenderer(id, path) { - const { windows } = this.props; - const window = windows[id]; - if (!window) return null; - return ( - <Window - key={window.id} - window={window} - /> - ); - } - - /** - * Update the redux store when the Mosaic is changed. - */ - mosaicChange(newLayout) { - const { updateWorkspaceMosaicLayout } = this.props; - updateWorkspaceMosaicLayout(newLayout); - } - - /** - * Used to determine whether or not a "new" layout should be autogenerated. - * If a Window is added or removed, generate that new layout and use that for - * this render. When the Mosaic changes, that will trigger a new store update. - */ - determineWorkspaceLayout() { - const { windows, workspace } = this.props; - const windowKeys = Object.keys(windows); - const leaveKeys = getLeaves(workspace.layout); - // Check every window is in the layout, and all layout windows are present - // in store - if (!windowKeys.every(e => leaveKeys.includes(e)) - || !leaveKeys.every(e => windowKeys.includes(e))) { - const newLayout = createBalancedTreeFromLeaves(windowKeys); - return newLayout; + workspaceByType() { + const { config, windows } = this.props; + switch (config.workspace.type) { + case 'mosaic': + return <WorkspaceMosaic windows={windows} />; + default: + return Object.values(windows).map(window => ( + <Window + key={window.id} + window={window} + /> + )); } - return null; } /** * render */ render() { - const { workspace } = this.props; - const newLayout = this.determineWorkspaceLayout(); return ( <div className={ns('workspace')}> - <Mosaic - renderTile={this.tileRenderer} - initialValue={newLayout || workspace.layout} - onChange={this.mosaicChange} - className="mirador-mosaic" - zeroStateView={<div />} - /> + {this.workspaceByType()} </div> ); } } Workspace.propTypes = { - updateWorkspaceMosaicLayout: PropTypes.func.isRequired, windows: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types - workspace: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + config: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types }; export default Workspace; diff --git a/src/components/WorkspaceMosaic.js b/src/components/WorkspaceMosaic.js new file mode 100644 index 0000000000000000000000000000000000000000..8a7b0b0eb6326d0a3c581bfbf5f10244a280fe9b --- /dev/null +++ b/src/components/WorkspaceMosaic.js @@ -0,0 +1,91 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + Mosaic, getLeaves, createBalancedTreeFromLeaves, +} from 'react-mosaic-component'; +import 'react-mosaic-component/react-mosaic-component.css'; +import Window from '../containers/Window'; + +/** + * Represents a work area that contains any number of windows + * @memberof Workspace + * @private + */ +class WorkspaceMosaic extends React.Component { + /** + */ + constructor(props) { + super(props); + + this.tileRenderer = this.tileRenderer.bind(this); + this.mosaicChange = this.mosaicChange.bind(this); + this.determineWorkspaceLayout = this.determineWorkspaceLayout.bind(this); + } + + /** + * Render a tile (Window) in the Mosaic. + */ + tileRenderer(id, path) { + const { windows } = this.props; + const window = windows[id]; + if (!window) return null; + return ( + <Window + key={window.id} + window={window} + /> + ); + } + + /** + * Update the redux store when the Mosaic is changed. + */ + mosaicChange(newLayout) { + const { updateWorkspaceMosaicLayout } = this.props; + updateWorkspaceMosaicLayout(newLayout); + } + + /** + * Used to determine whether or not a "new" layout should be autogenerated. + * If a Window is added or removed, generate that new layout and use that for + * this render. When the Mosaic changes, that will trigger a new store update. + */ + determineWorkspaceLayout() { + const { windows, workspace } = this.props; + const windowKeys = Object.keys(windows); + const leaveKeys = getLeaves(workspace.layout); + // Check every window is in the layout, and all layout windows are present + // in store + if (!windowKeys.every(e => leaveKeys.includes(e)) + || !leaveKeys.every(e => windowKeys.includes(e))) { + const newLayout = createBalancedTreeFromLeaves(windowKeys); + return newLayout; + } + return null; + } + + /** + */ + render() { + const { workspace } = this.props; + const newLayout = this.determineWorkspaceLayout(); + return ( + <Mosaic + renderTile={this.tileRenderer} + initialValue={newLayout || workspace.layout} + onChange={this.mosaicChange} + className="mirador-mosaic" + zeroStateView={<div />} + /> + ); + } +} + + +WorkspaceMosaic.propTypes = { + updateWorkspaceMosaicLayout: PropTypes.func.isRequired, + windows: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + workspace: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types +}; + +export default WorkspaceMosaic; diff --git a/src/config/settings.js b/src/config/settings.js index 258339b2c14090a3b0fb18690ae23cc00f5821cb..fe3312aa23bd651b817acb62eb2f9f24c60954a1 100644 --- a/src/config/settings.js +++ b/src/config/settings.js @@ -4,4 +4,7 @@ export default { defaultPosition: 'bottom', height: 150, }, + workspace: { + type: 'mosaic', + } }; diff --git a/src/containers/Workspace.js b/src/containers/Workspace.js index a74e9ae4b6e0220926ec69d550f2bbbf310a74cf..0bac3285eb2f9364e1f08423dd0dcad13d6013a9 100644 --- a/src/containers/Workspace.js +++ b/src/containers/Workspace.js @@ -10,21 +10,13 @@ import Workspace from '../components/Workspace'; */ const mapStateToProps = state => ( { + config: state.config, windows: state.windows, - workspace: state.workspace, } ); - -/** - * mapDispatchToProps - used to hook up connect to action creators - * @memberof Workspace - * @private - */ -const mapDispatchToProps = { updateWorkspaceMosaicLayout: actions.updateWorkspaceMosaicLayout }; - const enhance = compose( - connect(mapStateToProps, mapDispatchToProps), + connect(mapStateToProps), // further HOC go here ); diff --git a/src/containers/WorkspaceMosaic.js b/src/containers/WorkspaceMosaic.js new file mode 100644 index 0000000000000000000000000000000000000000..d6b4a388caf5c5551936a146aabc5d6f0a95b2da --- /dev/null +++ b/src/containers/WorkspaceMosaic.js @@ -0,0 +1,30 @@ +import { compose } from 'redux'; +import { connect } from 'react-redux'; +import * as actions from '../state/actions'; +import WorkspaceMosaic from '../components/WorkspaceMosaic'; + +/** + * mapStateToProps - to hook up connect + * @memberof Workspace + * @private + */ +const mapStateToProps = state => ( + { + workspace: state.workspace, + } +); + + +/** + * mapDispatchToProps - used to hook up connect to action creators + * @memberof Workspace + * @private + */ +const mapDispatchToProps = { updateWorkspaceMosaicLayout: actions.updateWorkspaceMosaicLayout }; + +const enhance = compose( + connect(mapStateToProps, mapDispatchToProps), + // further HOC go here +); + +export default enhance(WorkspaceMosaic);