Skip to content
Snippets Groups Projects
Commit 328cfd0a authored by Chris Beer's avatar Chris Beer
Browse files

Spike to convert the window top bar to the material framework; part of #1631

parent 9f886c7f
Branches
Tags
No related merge requests found
Showing
with 395 additions and 25 deletions
......@@ -7,11 +7,13 @@ describe('Basic end to end Mirador', () => {
expect(title).toBe('Mirador');
});
it('loads a manifest and displays it', async () => {
await expect(page).toClick('#addBtn');
await expect(page).toFill('#manifestURL', 'http://localhost:5000/api/sn904cj3439');
await expect(page).toClick('#fetchBtn');
// TODO: Refactor the app so we get rid of the wait
await page.waitFor(1000);
await expect(page).toMatchElement('li', { text: 'http://localhost:5000/api/sn904cj3439' });
await expect(page).toClick('li button');
await expect(page).toMatchElement(
'h3',
"Peter's San Francisco Locator. The Birds-Eye-View Map of the Exposition City. Published by Locator Publishing Co",
......
......@@ -3,6 +3,7 @@ describe('Mirador Invalid API Response Handler Test', () => {
await page.goto('http://127.0.0.1:4488/__tests__/integration/mirador/');
});
it('breaks Mirador', async () => {
await expect(page).toClick('#addBtn');
await expect(page).toFill('#manifestURL', 'http://localhost:5000/invalid');
await expect(page).toClick('#fetchBtn');
await page.waitFor(1000);
......
......@@ -3,6 +3,7 @@
describe('Mirador plugin use', () => {
beforeAll(async () => {
await page.goto('http://127.0.0.1:4488/__tests__/integration/mirador/plugins.html');
await expect(page).toClick('#addBtn');
await expect(page).toFill('#manifestURL', 'http://localhost:5000/api/sn904cj3439');
await expect(page).toClick('#fetchBtn');
// TODO: Refactor the app so we get rid of the wait
......
......@@ -3,11 +3,13 @@
describe('Thumbnail navigation', () => {
beforeAll(async () => {
await page.goto('http://127.0.0.1:4488/__tests__/integration/mirador/');
await expect(page).toClick('#addBtn');
await expect(page).toFill('#manifestURL', 'http://localhost:5000/api/019');
await expect(page).toClick('#fetchBtn');
// TODO: Refactor the app so we get rid of the wait
await page.waitFor(1000);
await expect(page).toClick('li button');
await page.waitFor(1000);
});
it('navigates a manifest using thumbnail navigation', async () => {
await expect(page).toMatchElement('.mirador-thumb-navigation');
......
......@@ -3,16 +3,19 @@ describe('Window actions', () => {
await page.goto('http://127.0.0.1:4488/__tests__/integration/mirador/');
});
it('opens a window and closes it', async () => {
await expect(page).toClick('#addBtn');
await expect(page).toFill('#manifestURL', 'http://localhost:5000/api/sn904cj3439');
await expect(page).toClick('#fetchBtn');
// TODO: Refactor the app so we get rid of the wait
await page.waitFor(1000);
await expect(page).toClick('li button');
await expect(page).toMatchElement('.mirador-window');
await page.waitFor(1000);
await expect(page).toClick('.mirador-window-close');
const numWindows = await page.evaluate(page => (
document.querySelectorAll('.mirador-window').length
)); // only default configed windows found
await page.waitFor(1000);
await expect(numWindows).toBe(2);
});
});
import React from 'react';
import { shallow } from 'enzyme';
import { store } from '../../../src/store';
import { WindowSideBar } from '../../../src/components/WindowSideBar';
describe('WindowSideBar', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<WindowSideBar store={store} windowId="1" classes={{}} />);
});
it('renders without an error', () => {
expect(wrapper.find('WithStyles(Drawer)').length).toBe(1);
});
});
import React from 'react';
import { shallow } from 'enzyme';
import { store } from '../../../src/store';
import { WindowSideBarButtons } from '../../../src/components/WindowSideBarButtons';
describe('WindowSideBarButtons', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<WindowSideBarButtons store={store} />);
});
it('renders without an error', () => {
expect(wrapper.find('Fragment').length).toBe(1);
});
});
......@@ -18,18 +18,23 @@ describe('WindowTopBar', () => {
manifest={manifestFixture}
windowId="foo"
removeWindow={mockRemoveWindow}
classes={{}}
/>,
);
});
it('renders without an error', () => {
expect(topBar.find('div.mirador-window-top-bar h3')
expect(topBar.dive().find('WithStyles(Toolbar)')
.dive()
.find('WithStyles(Typography)')
.dive()
.dive()
.text()).toBe('Fixture Label');
expect(topBar.find('button.mirador-window-close'));
expect(topBar.find('WithStyles(Button).mirador-window-close'));
});
it('calls the removeWindow prop when the close button is clicked', () => {
topBar.find('button').simulate('click');
topBar.find('WithStyles(Button)').simulate('click');
expect(mockRemoveWindow).toHaveBeenCalledTimes(1);
expect(mockRemoveWindow).toHaveBeenCalledWith('foo');
});
......
import React from 'react';
import { shallow } from 'enzyme';
import { store } from '../../../src/store';
import { WindowTopBarButtons } from '../../../src/components/WindowTopBarButtons';
describe('WindowTopBarButtons', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<WindowTopBarButtons store={store} />);
});
it('renders without an error', () => {
expect(wrapper.find('Fragment').length).toBe(1);
});
});
......@@ -9,18 +9,30 @@ describe('WorkspaceControlPanel', () => {
beforeEach(() => {
store.dispatch(actions.receiveManifest('foo', fixture));
store.dispatch(actions.receiveManifest('bar', fixture));
wrapper = shallow(<WorkspaceControlPanel store={store} />).dive();
wrapper = shallow(<WorkspaceControlPanel store={store} />).dive().dive();
});
it('renders without an error', () => {
expect(wrapper.find('div.mirador-workspace-control-panel').length).toBe(1);
expect(wrapper.find('WithStyles(Drawer)').length).toBe(1);
});
it('renders a list item for each manifest in the state', () => {
expect(wrapper.find('ul Connect(ManifestListItem)').length).toBe(2);
});
it('renders a Display component', () => {
expect(wrapper.find('Display').length).toBe(1);
describe('handleClose', () => {
it('resets the anchor state', () => {
wrapper.instance().handleClose();
expect(wrapper.dive().find('WithStyles(Menu)').props().open).toBe(false);
});
});
describe('handleClick', () => {
it('sets the anchor state', () => {
wrapper.instance().handleClick({ currentTarget: true });
expect(wrapper.dive().find('WithStyles(Menu)').props().open).toBe(true);
});
});
});
import React from 'react';
import { shallow } from 'enzyme';
import { store } from '../../../src/store';
import { WorkspaceControlPanelButtons } from '../../../src/components/WorkspaceControlPanelButtons';
describe('WorkspaceControlPanelButtons', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<WorkspaceControlPanelButtons store={store} />);
});
it('renders without an error', () => {
expect(wrapper.find('Fragment').length).toBe(1);
});
});
......@@ -21,6 +21,9 @@
],
"repository": "https://github.com/ProjectMirador/mirador",
"dependencies": {
"@material-ui/core": "^3.9.0",
"@material-ui/icons": "^3.0.2",
"classnames": "^2.2.6",
"css-ns": "^1.2.2",
"deepmerge": "^3.1.0",
"manifesto.js": "^3.0.9",
......
import React, { Component } from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import CssBaseline from '@material-ui/core/CssBaseline';
import { actions } from '../store';
import WorkspaceControlPanel from './WorkspaceControlPanel';
import ConnectedWorkspace from './Workspace';
......@@ -18,6 +20,7 @@ export class App extends Component {
render() {
return (
<div className={ns('app')}>
<CssBaseline />
<ConnectedWorkspace />
<WorkspaceControlPanel />
</div>
......
import React, { Component, Fragment } from 'react';
import classNames from 'classnames';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Drawer from '@material-ui/core/Drawer';
import miradorWithPlugins from '../lib/miradorWithPlugins';
/**
* CompanionWindow
*/
class CompanionWindow extends Component {
/**
* render
* @return
*/
render() {
const { windowId, classes, anchor } = this.props;
return (
<Drawer
variant="permanent"
className={classNames(classes.drawer)}
classes={{ paper: classNames(classes.drawer) }}
open
anchor={anchor}
PaperProps={{ style: { position: 'absolute' } }}
BackdropProps={{ style: { position: 'absolute' } }}
ModalProps={{
container: document.getElementById(windowId),
style: { position: 'absolute' },
}}
>
<div className={classes.toolbar} />
<Fragment />
</Drawer>
);
}
}
/**
* mapDispatchToProps - used to hook up connect to action creators
* @memberof ManifestListItem
* @private
*/
const mapDispatchToProps = dispatch => ({
});
CompanionWindow.propTypes = {
windowId: PropTypes.string.isRequired,
classes: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types,
anchor: PropTypes.string,
};
CompanionWindow.defaultProps = {
anchor: 'left',
};
/**
Override drawer styles to make a companion window
@private
*/
const styles = theme => ({
drawer: {
overflowX: 'hidden',
flexShrink: 0,
whiteSpace: 'nowrap',
zIndex: theme.zIndex.appBar - 2,
paddingLeft: 60,
},
grow: {
flexGrow: 1,
},
toolbar: theme.mixins.toolbar,
});
export default connect(null, mapDispatchToProps)(miradorWithPlugins(
withStyles(styles)(CompanionWindow),
));
......@@ -24,9 +24,9 @@ const handleOpenButtonClick = (event, manifest, addWindow) => {
* @memberof ManifestListItem
* @private
*/
export const ManifestListItem = ({ manifest, addWindow }) => (
export const ManifestListItem = ({ manifest, addWindow, handleClose }) => (
<li className={ns('manifest-list-item')}>
<button type="button" onClick={event => handleOpenButtonClick(event, manifest, addWindow)}>
<button type="button" onClick={(event) => { handleOpenButtonClick(event, manifest, addWindow); handleClose(); }}>
{manifest}
</button>
</li>
......@@ -35,6 +35,11 @@ export const ManifestListItem = ({ manifest, addWindow }) => (
ManifestListItem.propTypes = {
manifest: PropTypes.string.isRequired, // eslint-disable-line react/forbid-prop-types
addWindow: PropTypes.func.isRequired,
handleClose: PropTypes.func,
};
ManifestListItem.defaultProps = {
handleClose: () => {},
};
/**
......
......@@ -4,7 +4,9 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ns from '../config/css-ns';
import ConnectedWindowTopBar from './WindowTopBar';
import ConnectedWindowSideBar from './WindowSideBar';
import WindowViewer from './WindowViewer';
import ConnectedCompanionWindow from './CompanionWindow';
/**
* Represents a Window in the mirador workspace
......@@ -48,6 +50,14 @@ export class Window extends Component {
windowId={window.id}
manifest={manifest}
/>
<ConnectedCompanionWindow
windowId={window.id}
manifest={manifest}
/>
<ConnectedWindowSideBar
windowId={window.id}
manifest={manifest}
/>
{this.renderViewer()}
</div>
);
......
import React, { Component } from 'react';
import { compose } from 'redux';
import classNames from 'classnames';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Drawer from '@material-ui/core/Drawer';
import List from '@material-ui/core/List';
import ConnectedWindowSideBarButtons from './WindowSideBarButtons';
import miradorWithPlugins from '../lib/miradorWithPlugins';
/**
* WindowSideBar
*/
export class WindowSideBar extends Component {
/**
* render
* @return
*/
render() {
const {
windowId, classes, anchor,
} = this.props;
return (
<Drawer
variant="permanent"
className={classNames(classes.drawer)}
classes={{ paper: classNames(classes.drawer) }}
open
anchor={anchor}
PaperProps={{ style: { position: 'absolute' } }}
BackdropProps={{ style: { position: 'absolute' } }}
ModalProps={{
container: document.getElementById(windowId),
style: { position: 'absolute' },
}}
>
<div className={classes.toolbar} />
<List>
<ConnectedWindowSideBarButtons windowId={windowId} />
</List>
</Drawer>
);
}
}
/**
* mapDispatchToProps - used to hook up connect to action creators
* @memberof ManifestListItem
* @private
*/
const mapDispatchToProps = dispatch => ({
});
WindowSideBar.propTypes = {
windowId: PropTypes.string.isRequired,
classes: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types,
anchor: PropTypes.string,
};
WindowSideBar.defaultProps = {
anchor: 'left',
};
/**
Material UI style overrides
@private
*/
const styles = theme => ({
drawer: {
overflowX: 'hidden',
width: 55,
flexShrink: 0,
whiteSpace: 'nowrap',
zIndex: theme.zIndex.appBar - 1,
},
grow: {
flexGrow: 1,
},
toolbar: theme.mixins.toolbar,
});
const enhance = compose(
connect(null, mapDispatchToProps),
miradorWithPlugins,
withStyles(styles),
// further HOC go here
);
export default enhance(WindowSideBar);
import React, { Component } from 'react';
import miradorWithPlugins from '../lib/miradorWithPlugins';
/**
*
*/
export class WindowSideBarButtons extends Component {
/**
* render
*
* @return {type} description
*/
render() {
return (<></>);
}
}
export default miradorWithPlugins(WindowSideBarButtons);
......@@ -2,6 +2,11 @@ import React, { Component } from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import AppBar from '@material-ui/core/AppBar';
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import Toolbar from '@material-ui/core/Toolbar';
import { actions } from '../store';
import ConnectedWindowTopBarButtons from './WindowTopBarButtons';
import miradorWithPlugins from '../lib/miradorWithPlugins';
......@@ -29,13 +34,17 @@ export class WindowTopBar extends Component {
* @return
*/
render() {
const { removeWindow, windowId } = this.props;
const { removeWindow, windowId, classes } = this.props;
return (
<div className={ns('window-top-bar')}>
<h3>{this.titleContent()}</h3>
<AppBar position="absolute">
<Toolbar className={ns('window-top-bar')}>
<Typography variant="h3" noWrap color="inherit" className={classes.grow}>
{this.titleContent()}
</Typography>
<ConnectedWindowTopBarButtons windowId={windowId} />
<button type="button" className={ns('window-close')} aria-label="Close Window" onClick={() => removeWindow(windowId)}>&times;</button>
</div>
<Button color="inherit" className={ns('window-close')} aria-label="Close Window" onClick={() => removeWindow(windowId)}>&times;</Button>
</Toolbar>
</AppBar>
);
}
}
......@@ -51,15 +60,23 @@ WindowTopBar.propTypes = {
manifest: PropTypes.object, // eslint-disable-line react/forbid-prop-types
removeWindow: PropTypes.func.isRequired,
windowId: PropTypes.string.isRequired,
classes: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};
WindowTopBar.defaultProps = {
manifest: null,
};
const styles = {
grow: {
flexGrow: 1,
},
};
const enhance = compose(
connect(null, mapDispatchToProps),
miradorWithPlugins,
withStyles(styles),
// further HOC go here
);
......
import React, { Component } from 'react';
import { compose } from 'redux';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { connect } from 'react-redux';
import Display from './Display';
import List from '@material-ui/core/List';
import Fab from '@material-ui/core/Fab';
import AddIcon from '@material-ui/icons/Add';
import Menu from '@material-ui/core/Menu';
import { withStyles } from '@material-ui/core/styles';
import Drawer from '@material-ui/core/Drawer';
import ConnectedManifestForm from './ManifestForm';
import ConnectedManifestListItem from './ManifestListItem';
import ConnectedWorkspaceControlPanelButtons from './WorkspaceControlPanelButtons';
import ns from '../config/css-ns';
/**
......@@ -18,9 +25,12 @@ class WorkspaceControlPanel extends Component {
super(props);
this.state = {
lastRequested: '',
anchorEl: null,
};
this.setLastRequested = this.setLastRequested.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleClose = this.handleClose.bind(this);
}
/**
......@@ -34,34 +44,73 @@ class WorkspaceControlPanel extends Component {
});
}
/**
* @private
*/
handleClick(event) {
this.setState({
anchorEl: event.currentTarget,
});
}
/**
* @private
*/
handleClose() {
this.setState({
anchorEl: null,
});
}
/**
* render
* @return {String} - HTML markup for the component
*/
render() {
const { manifests } = this.props;
const { lastRequested } = this.state;
const { manifests, classes } = this.props;
const { lastRequested, anchorEl } = this.state;
const manifestList = Object.keys(manifests).map(manifest => (
<ConnectedManifestListItem
key={manifest}
manifest={manifest}
handleClose={this.handleClose}
/>
));
return (
<div className={ns('workspace-control-panel')}>
<ConnectedManifestForm setLastRequested={this.setLastRequested} />
<Drawer
className={classNames(classes.drawer, ns('workspace-control-panel'))}
variant="permanent"
classes={{ paper: classNames(classes.drawer) }}
open
>
<Menu id="add-form" anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={this.handleClose}>
<ConnectedManifestForm id="add-form" setLastRequested={this.setLastRequested} />
<ul>{manifestList}</ul>
{lastRequested}
</Menu>
<Display
manifest={manifests[lastRequested]}
/>
</div>
<List>
<Fab
color="primary"
id="addBtn"
aria-label="Add"
className={classes.fab}
aria-owns={anchorEl ? 'add-form' : undefined}
aria-haspopup="true"
onClick={this.handleClick}
>
<AddIcon />
</Fab>
<ConnectedWorkspaceControlPanelButtons />
</List>
</Drawer>
);
}
}
WorkspaceControlPanel.propTypes = {
manifests: PropTypes.instanceOf(Object).isRequired,
classes: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};
/**
......@@ -74,9 +123,19 @@ const mapStateToProps = state => (
manifests: state.manifests,
}
);
/**
* @private
*/
const styles = theme => ({
fab: {
margin: theme.spacing.unit,
},
});
const enhance = compose(
connect(mapStateToProps),
withStyles(styles),
// further HOC go here
);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment