Skip to content
Snippets Groups Projects
Commit 6e19d679 authored by Shaun Ellis's avatar Shaun Ellis Committed by Chris Beer
Browse files

Adds menu item, actions, and reducer for toggling zoom

parent c4684d42
Branches
Tags
No related merge requests found
......@@ -31,4 +31,13 @@ describe('workspace actions', () => {
expect(actions.updateWorkspaceMosaicLayout(options)).toEqual(expectedAction);
});
});
describe('toggleZoomControls', () => {
it('should set the zoom control visibility', () => {
const expectedAction = {
type: ActionTypes.TOGGLE_ZOOM_CONTROLS,
showZoomControls: true,
};
expect(actions.toggleZoomControls(true)).toEqual(expectedAction);
});
});
});
......@@ -6,9 +6,19 @@ import WindowList from '../../../src/containers/WindowList';
describe('WorkspaceMenu', () => {
let wrapper;
let handleClose;
const showZoomControls = false;
let toggleZoomControls;
beforeEach(() => {
handleClose = jest.fn();
wrapper = shallow(<WorkspaceMenu handleClose={handleClose} />);
toggleZoomControls = jest.fn();
wrapper = shallow(
<WorkspaceMenu
handleClose={handleClose}
showZoomControls={showZoomControls}
toggleZoomControls={toggleZoomControls}
/>,
);
});
it('renders without an error', () => {
......@@ -34,4 +44,11 @@ describe('WorkspaceMenu', () => {
expect(wrapper.find(WindowList).props().open).toBe(false);
});
});
describe('handleZoomToggleClick', () => {
it('resets the anchor state', () => {
wrapper.instance().handleZoomToggleClick();
expect(toggleZoomControls).toBeCalledWith(true);
});
});
});
import React from 'react';
import { shallow } from 'enzyme';
import ZoomControls from '../../../src/components/ZoomControls';
describe('ZoomControls', () => {
let wrapper;
const viewer = { x: 100, y: 100, zoom: 1 };
const showZoomControls = false;
let updateViewport;
beforeEach(() => {
updateViewport = jest.fn();
wrapper = shallow(
<ZoomControls
windowId="xyz"
viewer={viewer}
showZoomControls={showZoomControls}
updateViewport={updateViewport}
/>,
).dive();
});
describe('with showZoomControls=false', () => {
it('renders nothing unless asked', () => {
expect(wrapper.find('WithStyles(List)').length).toBe(0);
});
});
describe('with showZoomControls=true', () => {
beforeEach(() => {
updateViewport = jest.fn();
wrapper = shallow(
<ZoomControls
windowId="xyz"
viewer={viewer}
showZoomControls
updateViewport={updateViewport}
/>,
).dive();
});
it('renders a couple buttons', () => {
expect(wrapper.find('WithStyles(List)').length).toBe(1);
});
it('has a zoom-in button', () => {
const button = wrapper.find('WithStyles(IconButton)[aria-label="zoomIn"]');
expect(button.simulate('click'));
expect(updateViewport).toHaveBeenCalledTimes(1);
expect(updateViewport).toHaveBeenCalledWith('xyz', { x: 100, y: 100, zoom: 2 });
});
it('has a zoom-out button', () => {
const button = wrapper.find('WithStyles(IconButton)[aria-label="zoomOut"]');
expect(button.simulate('click'));
expect(updateViewport).toHaveBeenCalledTimes(1);
expect(updateViewport).toHaveBeenCalledWith('xyz', { x: 100, y: 100, zoom: 0.5 });
});
it('has a zoom reseet button', () => {
const button = wrapper.find('WithStyles(IconButton)[aria-label="zoomReset"]');
expect(button.simulate('click'));
expect(updateViewport).toHaveBeenCalledTimes(1);
expect(updateViewport).toHaveBeenCalledWith('xyz', { x: 100, y: 100, zoom: 1 });
});
});
describe('handleZoomInClick', () => {
it('increases the zoom value on Zoom-In', () => {
wrapper.instance().handleZoomInClick();
expect(updateViewport).toHaveBeenCalled();
});
});
});
......@@ -18,6 +18,14 @@ describe('workspace reducer', () => {
isFullscreenEnabled: true,
});
});
it('should handle TOGGLE_ZOOM_CONTROLS', () => {
expect(reducer([], {
type: ActionTypes.TOGGLE_ZOOM_CONTROLS,
showZoomControls: true,
})).toEqual({
showZoomControls: true,
});
});
it('should handle UPDATE_WORKSPACE_MOSAIC_LAYOUT', () => {
expect(reducer([], {
type: ActionTypes.UPDATE_WORKSPACE_MOSAIC_LAYOUT,
......
......@@ -23,6 +23,9 @@
"theme": "Theme",
"thumbnails": "Thumbnails",
"toggleWindowSideBar": "Toggle window sidebar",
"untitled": "[Untitled]"
"untitled": "[Untitled]",
"zoomIn": "Zoom in",
"zoomOut": "Zoom out",
"zoomReset": "Reset zoom"
}
}
......@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import OpenSeadragon from 'openseadragon';
import ns from '../config/css-ns';
import ZoomControls from '../containers/ZoomControls';
/**
* Represents a OpenSeadragonViewer in the mirador workspace. Responsible for mounting
......@@ -154,6 +155,7 @@ class OpenSeadragonViewer extends Component {
>
{ children }
</div>
<ZoomControls windowId={window.id} />
</>
);
}
......
......@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import Menu from '@material-ui/core/Menu';
import Divider from '@material-ui/core/Divider';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import LoupeIcon from '@material-ui/icons/Loupe';
import MenuItem from '@material-ui/core/MenuItem';
import Typography from '@material-ui/core/Typography';
import SaveAltIcon from '@material-ui/icons/SaveAlt';
......@@ -22,6 +23,7 @@ class WorkspaceMenu extends Component {
super(props);
this.state = {
windowList: {},
toggleZoom: {},
settings: {},
exportWorkspace: {},
};
......@@ -53,13 +55,29 @@ class WorkspaceMenu extends Component {
};
}
/**
* @private
*/
handleZoomToggleClick() {
const { toggleZoomControls, showZoomControls } = this.props;
toggleZoomControls(!showZoomControls);
}
/**
* render
* @return
*/
render() {
const { handleClose, anchorEl, t } = this.props;
const { windowList, settings, exportWorkspace } = this.state;
const {
handleClose, anchorEl, t, showZoomControls,
} = this.props;
const {
windowList,
toggleZoom,
settings,
exportWorkspace,
} = this.state;
return (
<>
......@@ -74,6 +92,16 @@ class WorkspaceMenu extends Component {
</ListItemIcon>
<Typography varient="inherit">{t('listAllOpenWindows')}</Typography>
</MenuItem>
<MenuItem
aria-haspopup="true"
onClick={(e) => { this.handleZoomToggleClick(e); handleClose(e); }}
aria-owns={toggleZoom.anchorEl ? 'toggle-zoom-menu' : undefined}
>
<ListItemIcon>
<LoupeIcon />
</ListItemIcon>
<Typography varient="inherit">{ showZoomControls ? 'Hide zoom controls' : 'Show Zoom Controls' }</Typography>
</MenuItem>
<Divider />
<MenuItem
aria-haspopup="true"
......@@ -101,6 +129,10 @@ class WorkspaceMenu extends Component {
open={Boolean(windowList.anchorEl)}
handleClose={this.handleMenuItemClose('windowList')}
/>
<WorkspaceSettings
open={Boolean(toggleZoom.open)}
handleClose={this.handleMenuItemClose('toggleZoom')}
/>
<WorkspaceSettings
open={Boolean(settings.open)}
handleClose={this.handleMenuItemClose('settings')}
......@@ -116,6 +148,8 @@ class WorkspaceMenu extends Component {
WorkspaceMenu.propTypes = {
handleClose: PropTypes.func.isRequired,
toggleZoomControls: PropTypes.func,
showZoomControls: PropTypes.bool,
anchorEl: PropTypes.object, // eslint-disable-line react/forbid-prop-types
t: PropTypes.func,
};
......@@ -123,6 +157,8 @@ WorkspaceMenu.propTypes = {
WorkspaceMenu.defaultProps = {
anchorEl: null,
t: key => key,
showZoomControls: false,
toggleZoomControls: () => {},
};
export default WorkspaceMenu;
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import IconButton from '@material-ui/core/IconButton';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import AddCircleIcon from '@material-ui/icons/AddCircle';
import RemoveCircleIcon from '@material-ui/icons/RemoveCircle';
import RefreshIcon from '@material-ui/icons/Refresh';
import PropTypes from 'prop-types';
/**
*/
class ZoomControls extends Component {
/**
* constructor -
*/
constructor(props) {
super(props);
this.handleZoomInClick = this.handleZoomInClick.bind(this);
this.handleZoomOutClick = this.handleZoomOutClick.bind(this);
this.handleZoomResetClick = this.handleZoomResetClick.bind(this);
}
/**
* @private
*/
handleZoomInClick() {
const { windowId, updateViewport, viewer } = this.props;
updateViewport(windowId, {
x: viewer.x,
y: viewer.y,
zoom: viewer.zoom * 2,
});
}
/**
* @private
*/
handleZoomOutClick() {
const { windowId, updateViewport, viewer } = this.props;
updateViewport(windowId, {
x: viewer.x,
y: viewer.y,
zoom: viewer.zoom / 2,
});
}
/**
* @private
*/
handleZoomResetClick() {
const { windowId, updateViewport, viewer } = this.props;
updateViewport(windowId, {
x: viewer.x,
y: viewer.y,
zoom: 1,
});
}
/**
* render
* @return
*/
render() {
const { showZoomControls, classes, t } = this.props;
if (!showZoomControls) {
return (
<>
</>
);
}
return (
<List className={classes.zoom_controls}>
<ListItem>
<IconButton aria-label={t('zoomIn')} onClick={this.handleZoomInClick}>
<AddCircleIcon />
</IconButton>
</ListItem>
<ListItem>
<IconButton aria-label={t('zoomOut')} onClick={this.handleZoomOutClick}>
<RemoveCircleIcon />
</IconButton>
</ListItem>
<ListItem>
<IconButton aria-label={t('zoomReset')} onClick={this.handleZoomResetClick}>
<RefreshIcon />
</IconButton>
</ListItem>
</List>
);
}
}
ZoomControls.propTypes = {
windowId: PropTypes.string,
showZoomControls: PropTypes.bool,
viewer: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number,
zoom: PropTypes.number,
}),
updateViewport: PropTypes.func,
classes: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
t: PropTypes.func,
};
ZoomControls.defaultProps = {
windowId: '',
showZoomControls: false,
viewer: {},
updateViewport: () => {},
t: key => key,
};
/**
* @private
*/
const styles = theme => ({
zoom_controls: {
position: 'absolute',
right: 0,
},
ListItem: {
paddingTop: 0,
paddingBottom: 0,
},
});
export default withStyles(styles)(ZoomControls);
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withNamespaces } from 'react-i18next';
import miradorWithPlugins from '../lib/miradorWithPlugins';
import * as actions from '../state/actions';
import WorkspaceMenu from '../components/WorkspaceMenu';
/**
* mapDispatchToProps - used to hook up connect to action creators
* @memberof ManifestListItem
* @private
*/
const mapDispatchToProps = { toggleZoomControls: actions.toggleZoomControls };
/**
* mapStateToProps - to hook up connect
* @memberof WindowViewer
* @private
*/
const mapStateToProps = state => (
{ showZoomControls: state.workspace.showZoomControls }
);
const enhance = compose(
connect(mapStateToProps, mapDispatchToProps),
withNamespaces(),
miradorWithPlugins,
// further HOC
......
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withNamespaces } from 'react-i18next';
import * as actions from '../state/actions';
import ZoomControls from '../components/ZoomControls';
/**
* mapStateToProps - to hook up connect
* @memberof Workspace
* @private
*/
const mapStateToProps = (state, props) => (
{
showZoomControls: state.workspace.showZoomControls,
viewer: state.windows[props.windowId].viewer,
}
);
/**
* mapDispatchToProps - used to hook up connect to action creators
* @memberof Workspace
* @private
*/
const mapDispatchToProps = { updateViewport: actions.updateViewport };
const enhance = compose(
connect(mapStateToProps, mapDispatchToProps),
withNamespaces(),
// further HOC go here
);
export default enhance(ZoomControls);
......@@ -15,6 +15,7 @@ const ActionTypes = {
SET_WINDOW_THUMBNAIL_POSITION: 'SET_WINDOW_THUMBNAIL_POSITION',
TOGGLE_WINDOW_SIDE_BAR: 'TOGGLE_WINDOW_SIDE_BAR',
TOGGLE_WINDOW_SIDE_BAR_PANEL: 'TOGGLE_WINDOW_SIDE_BAR_PANEL',
TOGGLE_ZOOM_CONTROLS: 'TOGGLE_ZOOM_CONTROLS',
UPDATE_CONFIG: 'UPDATE_CONFIG',
REMOVE_MANIFEST: 'REMOVE_MANIFEST',
REQUEST_INFO_RESPONSE: 'REQUEST_INFO_RESPONSE',
......
......@@ -11,6 +11,15 @@ export function setWorkspaceFullscreen(isFullscreenEnabled) {
return { type: ActionTypes.SET_WORKSPACE_FULLSCREEN, isFullscreenEnabled };
}
/**
* toggleZoomControls - action creator
* @param {Boolean} showZoomControls
* @memberof ActionCreators
*/
export function toggleZoomControls(showZoomControls) {
return { type: ActionTypes.TOGGLE_ZOOM_CONTROLS, showZoomControls };
}
/**
* updateWorkspaceMosaicLayout - action creator
*
......
......@@ -9,6 +9,8 @@ const workspaceReducer = (state = {}, action) => {
return { ...state, focusedWindowId: action.windowId };
case ActionTypes.SET_WORKSPACE_FULLSCREEN:
return { ...state, isFullscreenEnabled: action.isFullscreenEnabled };
case ActionTypes.TOGGLE_ZOOM_CONTROLS:
return { ...state, showZoomControls: action.showZoomControls };
case ActionTypes.UPDATE_WORKSPACE_MOSAIC_LAYOUT:
return { ...state, layout: action.layout };
default:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment