Skip to content
Snippets Groups Projects
Unverified Commit 9a50ea86 authored by Camille Villa's avatar Camille Villa Committed by GitHub
Browse files

Merge pull request #2255 from ProjectMirador/2135-selected-annos-toggle

Add toggle to switch between All and Selected annotations
parents 12825008 db222970
No related branches found
No related tags found
No related merge requests found
Showing
with 268 additions and 4 deletions
...@@ -133,4 +133,13 @@ describe('annotation actions', () => { ...@@ -133,4 +133,13 @@ describe('annotation actions', () => {
}; };
expect(actions.deselectAnnotation(windowId, canvasId, annotationId)).toEqual(expectedAction); expect(actions.deselectAnnotation(windowId, canvasId, annotationId)).toEqual(expectedAction);
}); });
it('handles the toggleAnnotationDisplay action', () => {
const windowId = 'wId1';
const expectedAction = {
type: ActionTypes.TOGGLE_ANNOTATION_DISPLAY,
windowId,
};
expect(actions.toggleAnnotationDisplay(windowId)).toEqual(expectedAction);
});
}); });
import React from 'react';
import { shallow } from 'enzyme';
import { AnnotationSettings } from '../../../src/components/AnnotationSettings';
/** */
function createWrapper(props) {
return shallow(
<AnnotationSettings
displayAll={false}
displayAllDisabled={false}
t={k => k}
toggleAnnotationDisplay={() => {}}
windowId="abc123"
{...props}
/>,
);
}
describe('AnnotationSettings', () => {
let control;
let wrapper;
const toggleAnnotationDisplayMock = jest.fn();
it('renders a FormControlLabel and a Switch', () => {
wrapper = createWrapper();
control = shallow(
wrapper.find('WithStyles(WithFormControlContext(FormControlLabel))').props().control,
);
expect(wrapper.find('WithStyles(WithFormControlContext(FormControlLabel))').length).toBe(1);
expect(control.find('Switch').length).toBe(1);
});
describe('control', () => {
it('is not checked when the displayAll prop is false', () => {
wrapper = createWrapper();
control = shallow(
wrapper.find('WithStyles(WithFormControlContext(FormControlLabel))').props().control,
);
expect(control.find('Switch').props().checked).toBe(false);
});
it('is checked when the displayAll prop is true', () => {
wrapper = createWrapper({ displayAll: true });
control = shallow(
wrapper.find('WithStyles(WithFormControlContext(FormControlLabel))').props().control,
);
expect(control.find('Switch').props().checked).toBe(true);
});
it('is disabled based on the displayAllDisabled prop', () => {
wrapper = createWrapper();
control = shallow(
wrapper.find('WithStyles(WithFormControlContext(FormControlLabel))').props().control,
);
expect(control.find('Switch').props().disabled).toBe(false);
wrapper = createWrapper({ displayAllDisabled: true });
control = shallow(
wrapper.find('WithStyles(WithFormControlContext(FormControlLabel))').props().control,
);
expect(control.find('Switch').props().disabled).toBe(true);
});
it('calls the toggleAnnotationDisplay prop function on change', () => {
wrapper = createWrapper({ toggleAnnotationDisplay: toggleAnnotationDisplayMock });
control = shallow(
wrapper.find('WithStyles(WithFormControlContext(FormControlLabel))').props().control,
);
control.find('Switch').props().onChange(); // trigger the onChange prop
expect(toggleAnnotationDisplayMock).toHaveBeenCalledTimes(1);
});
});
});
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import AnnotationSettings from '../../../src/containers/AnnotationSettings';
import { WindowSideBarAnnotationsPanel } from '../../../src/components/WindowSideBarAnnotationsPanel'; import { WindowSideBarAnnotationsPanel } from '../../../src/components/WindowSideBarAnnotationsPanel';
/** */ /** */
...@@ -27,6 +28,10 @@ describe('WindowSideBarAnnotationsPanel', () => { ...@@ -27,6 +28,10 @@ describe('WindowSideBarAnnotationsPanel', () => {
).toBe('annotations'); ).toBe('annotations');
}); });
it('has the AnnotationSettings component', () => {
expect(createWrapper().find(AnnotationSettings).length).toBe(1);
});
it('renders a list with a list item for each annotation', () => { it('renders a list with a list item for each annotation', () => {
wrapper = createWrapper({ wrapper = createWrapper({
annotations: [ annotations: [
......
...@@ -345,5 +345,17 @@ describe('windows reducer', () => { ...@@ -345,5 +345,17 @@ describe('windows reducer', () => {
expect(windowsReducer(beforeState, action)).toEqual(expectedState); expect(windowsReducer(beforeState, action)).toEqual(expectedState);
}); });
}); });
it('handles TOGGLE_ANNOTATION_DISPLAY by toggling the given window\'s displayAllAnnotation value', () => {
const beforeState = { abc123: { displayAllAnnotations: false } };
const action = {
type: ActionTypes.TOGGLE_ANNOTATION_DISPLAY, windowId: 'abc123',
};
const expectedState = {
abc123: { displayAllAnnotations: true },
};
expect(windowsReducer(beforeState, action)).toEqual(expectedState);
});
}); });
}); });
import { import {
getAllOrSelectedAnnotations,
getAnnotationResourcesByMotivation, getAnnotationResourcesByMotivation,
getIdAndContentOfResources, getIdAndContentOfResources,
getLanguagesFromConfigWithCurrent, getLanguagesFromConfigWithCurrent,
...@@ -166,3 +167,39 @@ it('getSelectedTargetAnnotationResources filters the annotation resources by the ...@@ -166,3 +167,39 @@ it('getSelectedTargetAnnotationResources filters the annotation resources by the
getSelectedTargetAnnotationResources(state, ['cid1'], ['annoId1', 'annoId2'])[0].resources.length, getSelectedTargetAnnotationResources(state, ['cid1'], ['annoId1', 'annoId2'])[0].resources.length,
).toBe(2); ).toBe(2);
}); });
describe('getAllOrSelectedAnnotations', () => {
it('returns all annotations if the given window is set to display all', () => {
const state = {
windows: {
abc123: { displayAllAnnotations: true },
},
annotations: {
cid1: {
annoId1: { id: 'annoId1', json: { resources: [{ '@id': 'annoId1' }, { '@id': 'annoId2' }] } },
},
},
};
expect(
getAllOrSelectedAnnotations(state, 'abc123', ['cid1'], ['annoId1'])[0].resources.length,
).toBe(2);
});
it('returns only selected annotations if the window is not set to display all', () => {
const state = {
windows: {
abc123: { displayAllAnnotations: false },
},
annotations: {
cid1: {
annoId1: { id: 'annoId1', json: { resources: [{ '@id': 'annoId1' }, { '@id': 'annoId2' }] } },
},
},
};
expect(
getAllOrSelectedAnnotations(state, 'abc123', ['cid1'], ['annoId1'])[0].resources.length,
).toBe(1);
});
});
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Switch from '@material-ui/core/Switch';
/**
* AnnotationSettings is a component to handle various annotation
* display settings in the Annotation companion window
*/
export class AnnotationSettings extends Component {
/**
* Returns the rendered component
*/
render() {
const {
displayAll, displayAllDisabled, t, toggleAnnotationDisplay,
} = this.props;
return (
<FormControlLabel
control={(
<Switch
checked={displayAll}
disabled={displayAllDisabled}
onChange={toggleAnnotationDisplay}
value={displayAll ? 'all' : 'select'}
/>
)}
label={t('displayAllAnnotations')}
labelPlacement="start"
/>
);
}
}
AnnotationSettings.propTypes = {
displayAll: PropTypes.bool.isRequired,
displayAllDisabled: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
toggleAnnotationDisplay: PropTypes.func.isRequired,
windowId: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
};
...@@ -4,6 +4,7 @@ import List from '@material-ui/core/List'; ...@@ -4,6 +4,7 @@ import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem'; import ListItem from '@material-ui/core/ListItem';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import { SanitizedHtml } from './SanitizedHtml'; import { SanitizedHtml } from './SanitizedHtml';
import AnnotationSettings from '../containers/AnnotationSettings';
import CompanionWindow from '../containers/CompanionWindow'; import CompanionWindow from '../containers/CompanionWindow';
import ns from '../config/css-ns'; import ns from '../config/css-ns';
...@@ -62,6 +63,7 @@ export class WindowSideBarAnnotationsPanel extends Component { ...@@ -62,6 +63,7 @@ export class WindowSideBarAnnotationsPanel extends Component {
} = this.props; } = this.props;
return ( return (
<CompanionWindow title={t('annotations')} paperClassName={ns('window-sidebar-annotation-panel')} windowId={windowId} id={id}> <CompanionWindow title={t('annotations')} paperClassName={ns('window-sidebar-annotation-panel')} windowId={windowId} id={id}>
<AnnotationSettings windowId={windowId} />
<div className={classes.section}> <div className={classes.section}>
<Typography variant="subtitle2">{t('showingNumAnnotations', { number: annotations.length })}</Typography> <Typography variant="subtitle2">{t('showingNumAnnotations', { number: annotations.length })}</Typography>
</div> </div>
......
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import * as actions from '../state/actions';
import { withPlugins } from '../extend';
import {
getAnnotationResourcesByMotivation,
getSelectedTargetAnnotations,
getSelectedCanvas,
} from '../state/selectors';
import { AnnotationSettings } from '../components/AnnotationSettings';
/**
* Mapping redux state to component props using connect
*/
const mapStateToProps = (state, { windowId }) => ({
displayAll: state.windows[windowId].displayAllAnnotations,
displayAllDisabled: getAnnotationResourcesByMotivation(
getSelectedTargetAnnotations(state, (getSelectedCanvas(state, { windowId }) || {}).id),
['oa:commenting', 'sc:painting'],
).length < 2,
});
/**
* Mapping redux action dispatches to component props using connect
*/
const mapDispatchToProps = (dispatch, { windowId }) => ({
toggleAnnotationDisplay: () => {
dispatch(actions.toggleAnnotationDisplay(windowId));
},
});
const enhance = compose(
withTranslation(),
connect(mapStateToProps, mapDispatchToProps),
withPlugins('AnnotationSettings'),
);
export default enhance(AnnotationSettings);
...@@ -7,9 +7,9 @@ import { withPlugins } from '../extend'; ...@@ -7,9 +7,9 @@ import { withPlugins } from '../extend';
import { OpenSeadragonViewer } from '../components/OpenSeadragonViewer'; import { OpenSeadragonViewer } from '../components/OpenSeadragonViewer';
import * as actions from '../state/actions'; import * as actions from '../state/actions';
import { import {
getAllOrSelectedAnnotations,
getCanvasLabel, getCanvasLabel,
getSelectedAnnotationIds, getSelectedAnnotationIds,
getSelectedTargetAnnotationResources,
} from '../state/selectors'; } from '../state/selectors';
/** /**
...@@ -22,8 +22,9 @@ const mapStateToProps = ({ ...@@ -22,8 +22,9 @@ const mapStateToProps = ({
}, { windowId, currentCanvases }) => ({ }, { windowId, currentCanvases }) => ({
viewer: viewers[windowId], viewer: viewers[windowId],
label: getCanvasLabel({ windows, manifests }, { windowId, canvasIndex: 'selected' }), label: getCanvasLabel({ windows, manifests }, { windowId, canvasIndex: 'selected' }),
annotations: getSelectedTargetAnnotationResources( annotations: getAllOrSelectedAnnotations(
{ annotations }, { annotations, windows },
windowId,
currentCanvases.map(c => c.id), currentCanvases.map(c => c.id),
getSelectedAnnotationIds({ windows }, windowId, currentCanvases.map(c => c.id)), getSelectedAnnotationIds({ windows }, windowId, currentCanvases.map(c => c.id)),
), ),
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
"currentItem": "Current item", "currentItem": "Current item",
"dark": "Dark theme", "dark": "Dark theme",
"dismiss": "Dismiss", "dismiss": "Dismiss",
"displayAllAnnotations": "Highlight all",
"downloadExport": "Download/Export workspace", "downloadExport": "Download/Export workspace",
"downloadExportWorkspace": "Download/Export workspace", "downloadExportWorkspace": "Download/Export workspace",
"elastic": "Elastic", "elastic": "Elastic",
......
...@@ -9,7 +9,7 @@ const ActionTypes = { ...@@ -9,7 +9,7 @@ const ActionTypes = {
RECEIVE_ANNOTATION_FAILURE: 'RECEIVE_ANNOTATION_FAILURE', RECEIVE_ANNOTATION_FAILURE: 'RECEIVE_ANNOTATION_FAILURE',
DESELECT_ANNOTATION: 'DESELECT_ANNOTATION', DESELECT_ANNOTATION: 'DESELECT_ANNOTATION',
SELECT_ANNOTATION: 'SELECT_ANNOTATION', SELECT_ANNOTATION: 'SELECT_ANNOTATION',
TOGGLE_ANNOTATION_DISPLAY: 'TOGGLE_ANNOTATION_DISPLAY',
FOCUS_WINDOW: 'FOCUS_WINDOW', FOCUS_WINDOW: 'FOCUS_WINDOW',
SET_WORKSPACE_FULLSCREEN: 'SET_WORKSPACE_FULLSCREEN', SET_WORKSPACE_FULLSCREEN: 'SET_WORKSPACE_FULLSCREEN',
......
...@@ -93,3 +93,15 @@ export function deselectAnnotation(windowId, canvasId, annotationId) { ...@@ -93,3 +93,15 @@ export function deselectAnnotation(windowId, canvasId, annotationId) {
type: ActionTypes.DESELECT_ANNOTATION, windowId, canvasId, annotationId, type: ActionTypes.DESELECT_ANNOTATION, windowId, canvasId, annotationId,
}; };
} }
/**
* toggleAnnotationDisplay - action creator
*
* @param {String} windowId
* @memberof ActionCreators
*/
export function toggleAnnotationDisplay(windowId) {
return {
type: ActionTypes.TOGGLE_ANNOTATION_DISPLAY, windowId,
};
}
...@@ -58,6 +58,7 @@ export function addWindow(options) { ...@@ -58,6 +58,7 @@ export function addWindow(options) {
companionWindowIds: [cwDefault, cwThumbs], companionWindowIds: [cwDefault, cwThumbs],
sideBarPanel: 'info', sideBarPanel: 'info',
rotation: null, rotation: null,
displayAllAnnotations: false,
selectedAnnotations: {}, selectedAnnotations: {},
view: 'single', view: 'single',
maximized: false, maximized: false,
......
...@@ -143,6 +143,14 @@ export const windowsReducer = (state = {}, action) => { ...@@ -143,6 +143,14 @@ export const windowsReducer = (state = {}, action) => {
}, },
}; };
} }
case ActionTypes.TOGGLE_ANNOTATION_DISPLAY:
return {
...state,
[action.windowId]: {
...state[action.windowId],
displayAllAnnotations: !state[action.windowId].displayAllAnnotations,
},
};
default: default:
return state; return state;
} }
......
...@@ -105,3 +105,20 @@ export function getSelectedTargetAnnotationResources(state, targetIds, annotatio ...@@ -105,3 +105,20 @@ export function getSelectedTargetAnnotationResources(state, targetIds, annotatio
resources: annotation.resources.filter(r => annotationIds && annotationIds.includes(r.id)), resources: annotation.resources.filter(r => annotationIds && annotationIds.includes(r.id)),
})); }));
} }
/**
* Return all of the given canvases annotations if the window
* is set to display all, otherwise only return selected
* @param {object} state
* @param {String} windowId
* @param {Array} targetIds
* @param {Array} annotationIds
* @return {Array}
*/
export function getAllOrSelectedAnnotations(state, windowId, targetIds, annotationIds) {
if (state.windows[windowId].displayAllAnnotations) {
return getSelectedTargetsAnnotations(state, targetIds);
}
return getSelectedTargetAnnotationResources(state, targetIds, annotationIds);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment