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
Branches
Tags
No related merge requests found
Showing
with 268 additions and 4 deletions
......@@ -133,4 +133,13 @@ describe('annotation actions', () => {
};
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 { shallow } from 'enzyme';
import AnnotationSettings from '../../../src/containers/AnnotationSettings';
import { WindowSideBarAnnotationsPanel } from '../../../src/components/WindowSideBarAnnotationsPanel';
/** */
......@@ -27,6 +28,10 @@ describe('WindowSideBarAnnotationsPanel', () => {
).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', () => {
wrapper = createWrapper({
annotations: [
......
......@@ -345,5 +345,17 @@ describe('windows reducer', () => {
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 {
getAllOrSelectedAnnotations,
getAnnotationResourcesByMotivation,
getIdAndContentOfResources,
getLanguagesFromConfigWithCurrent,
......@@ -166,3 +167,39 @@ it('getSelectedTargetAnnotationResources filters the annotation resources by the
getSelectedTargetAnnotationResources(state, ['cid1'], ['annoId1', 'annoId2'])[0].resources.length,
).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';
import ListItem from '@material-ui/core/ListItem';
import Typography from '@material-ui/core/Typography';
import { SanitizedHtml } from './SanitizedHtml';
import AnnotationSettings from '../containers/AnnotationSettings';
import CompanionWindow from '../containers/CompanionWindow';
import ns from '../config/css-ns';
......@@ -62,6 +63,7 @@ export class WindowSideBarAnnotationsPanel extends Component {
} = this.props;
return (
<CompanionWindow title={t('annotations')} paperClassName={ns('window-sidebar-annotation-panel')} windowId={windowId} id={id}>
<AnnotationSettings windowId={windowId} />
<div className={classes.section}>
<Typography variant="subtitle2">{t('showingNumAnnotations', { number: annotations.length })}</Typography>
</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';
import { OpenSeadragonViewer } from '../components/OpenSeadragonViewer';
import * as actions from '../state/actions';
import {
getAllOrSelectedAnnotations,
getCanvasLabel,
getSelectedAnnotationIds,
getSelectedTargetAnnotationResources,
} from '../state/selectors';
/**
......@@ -22,8 +22,9 @@ const mapStateToProps = ({
}, { windowId, currentCanvases }) => ({
viewer: viewers[windowId],
label: getCanvasLabel({ windows, manifests }, { windowId, canvasIndex: 'selected' }),
annotations: getSelectedTargetAnnotationResources(
{ annotations },
annotations: getAllOrSelectedAnnotations(
{ annotations, windows },
windowId,
currentCanvases.map(c => c.id),
getSelectedAnnotationIds({ windows }, windowId, currentCanvases.map(c => c.id)),
),
......
......@@ -18,6 +18,7 @@
"currentItem": "Current item",
"dark": "Dark theme",
"dismiss": "Dismiss",
"displayAllAnnotations": "Highlight all",
"downloadExport": "Download/Export workspace",
"downloadExportWorkspace": "Download/Export workspace",
"elastic": "Elastic",
......
......@@ -9,7 +9,7 @@ const ActionTypes = {
RECEIVE_ANNOTATION_FAILURE: 'RECEIVE_ANNOTATION_FAILURE',
DESELECT_ANNOTATION: 'DESELECT_ANNOTATION',
SELECT_ANNOTATION: 'SELECT_ANNOTATION',
TOGGLE_ANNOTATION_DISPLAY: 'TOGGLE_ANNOTATION_DISPLAY',
FOCUS_WINDOW: 'FOCUS_WINDOW',
SET_WORKSPACE_FULLSCREEN: 'SET_WORKSPACE_FULLSCREEN',
......
......@@ -93,3 +93,15 @@ export function deselectAnnotation(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) {
companionWindowIds: [cwDefault, cwThumbs],
sideBarPanel: 'info',
rotation: null,
displayAllAnnotations: false,
selectedAnnotations: {},
view: 'single',
maximized: false,
......
......@@ -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:
return state;
}
......
......@@ -105,3 +105,20 @@ export function getSelectedTargetAnnotationResources(state, targetIds, annotatio
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 to comment