From 5dd569d0ff421585d4fa14b89c3b076b6c6f99f2 Mon Sep 17 00:00:00 2001 From: Chris Beer <cabeer@stanford.edu> Date: Fri, 15 May 2020 09:37:12 -0700 Subject: [PATCH] Extract configuration for view types --- .../src/components/WindowViewSettings.test.js | 4 +- __tests__/src/selectors/windows.test.js | 37 +++++++++ src/components/WindowViewSettings.js | 75 ++++++++----------- src/config/settings.js | 8 +- src/containers/WindowViewSettings.js | 3 +- src/state/selectors/config.js | 5 ++ src/state/selectors/windows.js | 42 ++++++++--- 7 files changed, 119 insertions(+), 55 deletions(-) diff --git a/__tests__/src/components/WindowViewSettings.test.js b/__tests__/src/components/WindowViewSettings.test.js index b33a2e81d..bd19497b2 100644 --- a/__tests__/src/components/WindowViewSettings.test.js +++ b/__tests__/src/components/WindowViewSettings.test.js @@ -7,11 +7,12 @@ import { WindowViewSettings } from '../../../src/components/WindowViewSettings'; /** create wrapper */ function createWrapper(props) { - return shallow( + return mount( <WindowViewSettings classes={{}} windowId="xyz" setWindowViewType={() => {}} + viewTypes={['single', 'book', 'scroll', 'gallery']} windowViewType="single" {...props} />, @@ -64,6 +65,7 @@ describe('WindowViewSettings', () => { classes={{}} windowId="xyz" setWindowViewType={() => {}} + viewTypes={['single', 'book', 'scroll', 'gallery']} windowViewType="single" />, ); diff --git a/__tests__/src/selectors/windows.test.js b/__tests__/src/selectors/windows.test.js index 5bc269967..3db842886 100644 --- a/__tests__/src/selectors/windows.test.js +++ b/__tests__/src/selectors/windows.test.js @@ -11,6 +11,7 @@ import { getWindowManifests, getWindows, getMaximizedWindowsIds, + getAllowedWindowViewTypes, } from '../../../src/state/selectors/windows'; describe('getWindows', () => { @@ -104,6 +105,12 @@ describe('getWindowViewType', () => { config: { window: { defaultView: 'default', + views: [ + { behaviors: ['individuals'], key: 'single' }, + { behaviors: ['paged'], key: 'book' }, + { behaviors: ['continuous'], key: 'scroll' }, + { key: 'gallery' }, + ], }, }, manifests: { @@ -150,6 +157,36 @@ describe('getWindowViewType', () => { }); }); +describe('getAllowedWindowViewTypes', () => { + const state = { + config: { + window: { + defaultView: 'single', + views: [ + { behaviors: ['individuals'], key: 'single' }, + { behaviors: ['paged'], key: 'book' }, + { behaviors: ['continuous'], key: 'scroll' }, + { key: 'gallery' }, + ], + }, + }, + manifests: { + x: { json: { ...manifestFixture001 } }, + y: { json: { ...manifestFixture015 } }, + }, + }; + + it('should return unrestricted view types', () => { + const received = getAllowedWindowViewTypes(state, { manifestId: 'x' }); + expect(received).toEqual(['single', 'gallery']); + }); + + it('should return view types where behaviors match', () => { + const received = getAllowedWindowViewTypes(state, { manifestId: 'y' }); + expect(received).toEqual(['single', 'book', 'gallery']); + }); +}); + describe('getViewer', () => { const state = { viewers: { diff --git a/src/components/WindowViewSettings.js b/src/components/WindowViewSettings.js index 92d7116a9..f8faba985 100644 --- a/src/components/WindowViewSettings.js +++ b/src/components/WindowViewSettings.js @@ -4,6 +4,7 @@ import FormControlLabel from '@material-ui/core/FormControlLabel'; import MenuItem from '@material-ui/core/MenuItem'; import ListSubheader from '@material-ui/core/ListSubheader'; import SingleIcon from '@material-ui/icons/CropOriginalSharp'; +import ScrollViewIcon from '@material-ui/icons/ViewColumn'; import PropTypes from 'prop-types'; import BookViewIcon from './icons/BookViewIcon'; import GalleryViewIcon from './icons/GalleryViewIcon'; @@ -55,53 +56,41 @@ export class WindowViewSettings extends Component { */ render() { const { - classes, handleClose, t, windowViewType, + classes, handleClose, t, windowViewType, viewTypes, } = this.props; + if (viewTypes.length === 0) return null; + + const iconMap = { + book: BookViewIcon, + gallery: GalleryViewIcon, + scroll: ScrollViewIcon, + single: SingleIcon, + }; + + /** */ + const ViewItem = ({ Icon, value }) => ( + <MenuItem + className={classes.MenuItem} + ref={ref => this.handleSelectedRef(ref)} + onClick={() => { this.handleChange(value); handleClose(); }} + > + <FormControlLabel + value={value} + classes={{ label: windowViewType === value ? classes.selectedLabel : classes.label }} + control={Icon && <Icon color={windowViewType === value ? 'secondary' : undefined} />} + label={t(value)} + labelPlacement="bottom" + /> + </MenuItem> + ); + return ( <> <ListSubheader role="presentation" disableSticky tabIndex="-1">{t('view')}</ListSubheader> - - <MenuItem - className={classes.MenuItem} - ref={ref => this.handleSelectedRef(ref)} - onClick={() => { this.handleChange('single'); handleClose(); }} - > - <FormControlLabel - value="single" - classes={{ label: windowViewType === 'single' ? classes.selectedLabel : classes.label }} - control={<SingleIcon color={windowViewType === 'single' ? 'secondary' : undefined} />} - label={t('single')} - labelPlacement="bottom" - /> - </MenuItem> - <MenuItem className={classes.MenuItem} onClick={() => { this.handleChange('book'); handleClose(); }}> - <FormControlLabel - value="book" - classes={{ label: windowViewType === 'book' ? classes.selectedLabel : classes.label }} - control={<BookViewIcon color={windowViewType === 'book' ? 'secondary' : undefined} />} - label={t('book')} - labelPlacement="bottom" - /> - </MenuItem> - <MenuItem className={classes.MenuItem} onClick={() => { this.handleChange('scroll'); handleClose(); }}> - <FormControlLabel - value="scroll" - classes={{ label: windowViewType === 'scroll' ? classes.selectedLabel : classes.label }} - control={<BookViewIcon color={windowViewType === 'scroll' ? 'secondary' : undefined} />} - label={t('scroll')} - labelPlacement="bottom" - /> - </MenuItem> - <MenuItem className={classes.MenuItem} onClick={() => { this.handleChange('gallery'); handleClose(); }}> - <FormControlLabel - value="gallery" - classes={{ label: windowViewType === 'gallery' ? classes.selectedLabel : classes.label }} - control={<GalleryViewIcon color={windowViewType === 'gallery' ? 'secondary' : undefined} />} - label={t('gallery')} - labelPlacement="bottom" - /> - </MenuItem> + {viewTypes.map(value => ( + <ViewItem Icon={iconMap[value]} key={value} value={value} /> + ))} </> ); } @@ -112,10 +101,12 @@ WindowViewSettings.propTypes = { handleClose: PropTypes.func, setWindowViewType: PropTypes.func.isRequired, t: PropTypes.func, + viewTypes: PropTypes.arrayOf(PropTypes.string), windowId: PropTypes.string.isRequired, windowViewType: PropTypes.string.isRequired, }; WindowViewSettings.defaultProps = { handleClose: () => {}, t: key => key, + viewTypes: [], }; diff --git a/src/config/settings.js b/src/config/settings.js index a81460c65..419a0c62f 100644 --- a/src/config/settings.js +++ b/src/config/settings.js @@ -242,7 +242,13 @@ export default { canvas: true, annotations: true, search: true, - } + }, + views: [ + { key: 'single', behaviors: ['individuals'] }, + { key: 'book', behaviors: ['paged'] }, + { key: 'scroll', behaviors: ['continuous'] }, + { key: 'gallery' }, + ], }, windows: [ // Array of windows to be open when mirador initializes (each object should at least provide a manifestId key with the value of the IIIF presentation manifest to load) /** diff --git a/src/containers/WindowViewSettings.js b/src/containers/WindowViewSettings.js index e4b47ae70..6fc417f28 100644 --- a/src/containers/WindowViewSettings.js +++ b/src/containers/WindowViewSettings.js @@ -4,7 +4,7 @@ import { withTranslation } from 'react-i18next'; import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; -import { getWindowViewType } from '../state/selectors'; +import { getAllowedWindowViewTypes, getWindowViewType } from '../state/selectors'; import { WindowViewSettings } from '../components/WindowViewSettings'; /** @@ -21,6 +21,7 @@ const mapDispatchToProps = { setWindowViewType: actions.setWindowViewType }; */ const mapStateToProps = (state, { windowId }) => ( { + viewTypes: getAllowedWindowViewTypes(state, { windowId }), windowViewType: getWindowViewType(state, { windowId }), } ); diff --git a/src/state/selectors/config.js b/src/state/selectors/config.js index 6612fceab..ed7981d2b 100644 --- a/src/state/selectors/config.js +++ b/src/state/selectors/config.js @@ -52,6 +52,11 @@ export const getDefaultView = createSelector( ({ window }) => window && window.defaultView, ); +export const getViewConfigs = createSelector( + [getConfig], + ({ window }) => (window && window.views) || [], +); + export const getThemeDirection = createSelector( [getConfig], ({ theme }) => theme.direction || 'ltr', diff --git a/src/state/selectors/windows.js b/src/state/selectors/windows.js index 67c490f87..c24ca52d8 100644 --- a/src/state/selectors/windows.js +++ b/src/state/selectors/windows.js @@ -5,7 +5,7 @@ import { getManifestViewingHint, getManifestoInstance, } from './manifests'; -import { getDefaultView } from './config'; +import { getDefaultView, getViewConfigs } from './config'; import { getWorkspaceType } from './workspace'; /** @@ -85,19 +85,41 @@ export const getWindowViewType = createSelector( getManifestViewingHint, getManifestBehaviors, getDefaultView, + getViewConfigs, ], - (window, manifestViewingHint, manifestBehaviors, defaultView) => { - const lookup = { - continuous: 'scroll', - individuals: 'single', - paged: 'book', - }; - return (window && window.view) - || lookup[manifestBehaviors.find(b => lookup[b]) || manifestViewingHint] - || defaultView; + (window, manifestViewingHint, manifestBehaviors, defaultView, viewConfig) => { + if (window && window.view) return window.view; + + const config = viewConfig.find(view => ( + view.behaviors + && view.behaviors.some(b => manifestViewingHint === b || manifestBehaviors.includes(b)) + )); + + return (config && config.key) || defaultView; }, ); +/** */ +export const getAllowedWindowViewTypes = createSelector( + [ + getManifestViewingHint, + getManifestBehaviors, + getDefaultView, + getViewConfigs, + ], + (manifestViewingHint, manifestBehaviors, defaultView, viewConfig) => ( + viewConfig.reduce((allowedViews, view) => { + if ( + view.key === defaultView + || !view.behaviors + || view.behaviors.some(b => ( + manifestViewingHint === b || manifestBehaviors.includes(b) + ))) allowedViews.push(view.key); + return allowedViews; + }, []) + ), +); + export const getViewer = createSelector( [ state => state.viewers, -- GitLab