From ed9ec05dc345e900dad1d3133a4ee79f9df2a21a Mon Sep 17 00:00:00 2001 From: Jack Reed <phillipjreed@gmail.com> Date: Fri, 1 Feb 2019 10:38:41 -0700 Subject: [PATCH] Create a seam used to render other types of workspaces --- __tests__/src/components/Workspace.test.js | 65 ++++--------- .../src/components/WorkspaceMosaic.test.js | 69 ++++++++++++++ src/components/Workspace.js | 72 ++++----------- src/components/WorkspaceMosaic.js | 91 +++++++++++++++++++ src/config/settings.js | 3 + src/containers/Workspace.js | 12 +-- src/containers/WorkspaceMosaic.js | 30 ++++++ 7 files changed, 229 insertions(+), 113 deletions(-) create mode 100644 __tests__/src/components/WorkspaceMosaic.test.js create mode 100644 src/components/WorkspaceMosaic.js create mode 100644 src/containers/WorkspaceMosaic.js diff --git a/__tests__/src/components/Workspace.test.js b/__tests__/src/components/Workspace.test.js index fde08305b..138523c2b 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 000000000..64b2a0131 --- /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 de8694c11..35cef899b 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 000000000..8a7b0b0eb --- /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 258339b2c..fe3312aa2 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 a74e9ae4b..0bac3285e 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 000000000..d6b4a388c --- /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); -- GitLab