diff --git a/__tests__/src/components/CanvasLayers.test.js b/__tests__/src/components/CanvasLayers.test.js index a9e9bb4cc43272f6818be8a89da175babec71369..e831f31a994d879be0cc51a1f000a0d8a989384b 100644 --- a/__tests__/src/components/CanvasLayers.test.js +++ b/__tests__/src/components/CanvasLayers.test.js @@ -1,11 +1,9 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { Utils } from 'manifesto.js'; import Input from '@material-ui/core/Input'; import Slider from '@material-ui/core/Slider'; import Typography from '@material-ui/core/Typography'; import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; -import manifestFixtureHamilton from '../../fixtures/version-2/hamilton.json'; import { CanvasLayers } from '../../../src/components/CanvasLayers'; import IIIFThumbnail from '../../../src/containers/IIIFThumbnail'; @@ -13,7 +11,7 @@ import IIIFThumbnail from '../../../src/containers/IIIFThumbnail'; function createWrapper(props) { return shallow( <CanvasLayers - canvas={{ id: 'foo' }} + canvasId="foo" classes={{}} index={0} label="A Canvas Label" @@ -39,7 +37,7 @@ describe('CanvasLayers', () => { it('renders canvas layers in a list', () => { const wrapper = createWrapper({ - canvas: Utils.parseManifest(manifestFixtureHamilton).getSequences()[0].getCanvasByIndex(0), + canvasId: 'https://prtd.app/hamilton/canvas/p1.json', layers: [ { id: 'https://prtd.app/image/iiif/2/hamilton%2fHL_524_1r_00_PSC/full/862,1024/0/default.jpg' }, { id: 'https://prtd.app/image/iiif/2/hamilton%2fHL_524_1r_00_TS_Blue/full/862,1024/0/default.png' }, @@ -96,7 +94,7 @@ describe('CanvasLayers', () => { beforeEach(() => { updateLayers = jest.fn(); const wrapper = createWrapper({ - canvas: Utils.parseManifest(manifestFixtureHamilton).getSequences()[0].getCanvasByIndex(0), + canvasId: 'https://prtd.app/hamilton/canvas/p1.json', layers: [ { id: 'https://prtd.app/image/iiif/2/hamilton%2fHL_524_1r_00_PSC/full/862,1024/0/default.jpg' }, { id: 'https://prtd.app/image/iiif/2/hamilton%2fHL_524_1r_00_TS_Blue/full/862,1024/0/default.png' }, diff --git a/__tests__/src/components/IIIFThumbnail.test.js b/__tests__/src/components/IIIFThumbnail.test.js index 6975f5a4f14c8f7bec7ef066a025daa44c01ffe6..fa6eb72fa33ef5fb6e93b254b532ac2ede40cc57 100644 --- a/__tests__/src/components/IIIFThumbnail.test.js +++ b/__tests__/src/components/IIIFThumbnail.test.js @@ -18,9 +18,9 @@ function createWrapper(props) { describe('IIIFThumbnail', () => { let wrapper; const url = 'http://example.com/iiif/image'; - const image = { height: 120, url, width: 100 }; + const thumbnail = { height: 120, url, width: 100 }; beforeEach(() => { - wrapper = createWrapper({ image }); + wrapper = createWrapper({ thumbnail }); }); it('renders properly', () => { @@ -55,36 +55,36 @@ describe('IIIFThumbnail', () => { }); it('can be constrained by maxHeight', () => { - wrapper = createWrapper({ image, maxHeight: 100 }); + wrapper = createWrapper({ maxHeight: 100, thumbnail }); expect(wrapper.find('img').props().style).toMatchObject({ height: 100, width: 'auto' }); }); it('can be constrained by maxWidth', () => { - wrapper = createWrapper({ image, maxWidth: 80 }); + wrapper = createWrapper({ maxWidth: 80, thumbnail }); expect(wrapper.find('img').props().style).toMatchObject({ height: 'auto', width: 80 }); }); it('can be constrained by maxWidth and maxHeight', () => { - wrapper = createWrapper({ image, maxHeight: 90, maxWidth: 50 }); + wrapper = createWrapper({ maxHeight: 90, maxWidth: 50, thumbnail }); expect(wrapper.find('img').props().style).toMatchObject({ height: 60, width: 50 }); }); it('relaxes constraints when the image dimensions are unknown', () => { - wrapper = createWrapper({ image: { url } }); + wrapper = createWrapper({ thumbnail: { url } }); expect(wrapper.find('img').props().style).toMatchObject({ height: 'auto', width: 'auto' }); }); it('constrains what it can when the image dimensions are unknown', () => { - wrapper = createWrapper({ image: { height: 120, url }, maxHeight: 90 }); + wrapper = createWrapper({ maxHeight: 90, thumbnail: { height: 120, url } }); expect(wrapper.find('img').props().style).toMatchObject({ height: 90, width: 'auto' }); }); it('renders a provided label', () => { wrapper = createWrapper({ - classes: { label: 'label' }, image, label: 'Some label', labelled: true, + classes: { label: 'label' }, label: 'Some label', labelled: true, thumbnail, }); expect( wrapper.find('div.label').at(0).matchesElement( @@ -94,7 +94,7 @@ describe('IIIFThumbnail', () => { }); it('renders children', () => { - wrapper = createWrapper({ children: <span id="hi" />, image }); + wrapper = createWrapper({ children: <span id="hi" />, thumbnail }); expect(wrapper.find('span').length).toEqual(1); }); }); diff --git a/__tests__/src/components/LayersPanel.test.js b/__tests__/src/components/LayersPanel.test.js index d4877ce1f25ea03bc445adde540dab650c013f1f..769bde8c999b3eee6f71639f07bbe27682d98da4 100644 --- a/__tests__/src/components/LayersPanel.test.js +++ b/__tests__/src/components/LayersPanel.test.js @@ -19,11 +19,8 @@ function createWrapper(props) { describe('LayersPanel', () => { it('renders layers for each canvas', () => { - const canvases = [ - { id: 'a' }, - { id: 'b' }, - ]; - const wrapper = createWrapper({ canvases }); + const canvasIds = ['a', 'b']; + const wrapper = createWrapper({ canvasIds }); expect(wrapper.find(CanvasLayers).length).toBe(2); expect(wrapper.find(CanvasLayers).at(0).props()).toMatchObject({ diff --git a/__tests__/src/components/SidebarIndexItem.test.js b/__tests__/src/components/SidebarIndexItem.test.js index c26f2e5dd8009b351dde28436ae73fa20a0bf8fc..fb4295ffed531ae77b04bc7022d0e0fa90f4403a 100644 --- a/__tests__/src/components/SidebarIndexItem.test.js +++ b/__tests__/src/components/SidebarIndexItem.test.js @@ -7,7 +7,7 @@ import { SidebarIndexItem } from '../../../src/components/SidebarIndexItem'; function createWrapper(props) { return shallow( <SidebarIndexItem - canvas={{ label: 'yolo' }} + label="yolo" classes={{}} {...props} />, diff --git a/__tests__/src/components/SidebarIndexList.test.js b/__tests__/src/components/SidebarIndexList.test.js index da8dc6a7af70133e1aa8cb304d633b3f7d98b39d..03cad91b6e943a2cf814c316b11e2667a94443e1 100644 --- a/__tests__/src/components/SidebarIndexList.test.js +++ b/__tests__/src/components/SidebarIndexList.test.js @@ -23,7 +23,7 @@ function createWrapper(props) { setCanvas={() => {}} config={{ canvasNavigation: { height: 100 } }} updateVariant={() => {}} - selectedCanvases={[canvases[1]]} + selectedCanvasIds={[canvases[1].id]} {...props} />, ); diff --git a/__tests__/src/components/SidebarIndexThumbnail.test.js b/__tests__/src/components/SidebarIndexThumbnail.test.js index 997df3013c3606f6ebd927b5998674689ce0fb29..a2c219a2f9df6def5bb5b21297121d070a623f7e 100644 --- a/__tests__/src/components/SidebarIndexThumbnail.test.js +++ b/__tests__/src/components/SidebarIndexThumbnail.test.js @@ -10,8 +10,8 @@ import IIIFThumbnail from '../../../src/containers/IIIFThumbnail'; function createWrapper(props) { return shallow( <SidebarIndexThumbnail - canvas={{ label: 'yolo' }} - otherCanvas={Utils.parseManifest(fixture).getSequences()[0].getCanvases()[1]} + canvas={Utils.parseManifest(fixture).getSequences()[0].getCanvases()[1]} + label="yolo" classes={{}} config={{ canvasNavigation: { height: 200, width: 100 } }} {...props} diff --git a/__tests__/src/components/ThumbnailCanvasGrouping.test.js b/__tests__/src/components/ThumbnailCanvasGrouping.test.js index dfcfcb7312159ae3d22e1b8fbf7c73ffaf003c2d..ba27c5a71cbeaeab2f1a0c60f4c0c81271ef5ff8 100644 --- a/__tests__/src/components/ThumbnailCanvasGrouping.test.js +++ b/__tests__/src/components/ThumbnailCanvasGrouping.test.js @@ -28,7 +28,7 @@ describe('ThumbnailCanvasGrouping', () => { let setCanvas; const data = { canvasGroupings: new CanvasGroupings(Utils.parseManifest(manifestJson) - .getSequences()[0].getCanvases()), + .getSequences()[0].getCanvases()).groupings(), height: 131, position: 'far-bottom', }; diff --git a/__tests__/src/components/ThumbnailNavigation.test.js b/__tests__/src/components/ThumbnailNavigation.test.js index e86aa08966ab0bf28fc79e3e42d9d3f2986fc5c4..4bfbf173ff2859e039055e7ee0f7b78d9a7b9952 100644 --- a/__tests__/src/components/ThumbnailNavigation.test.js +++ b/__tests__/src/components/ThumbnailNavigation.test.js @@ -12,7 +12,9 @@ function createWrapper(props, fixture = manifestJson) { return shallow( <ThumbnailNavigation canvasGroupings={ - new CanvasGroupings(Utils.parseManifest(fixture).getSequences()[0].getCanvases()) + new CanvasGroupings( + Utils.parseManifest(fixture).getSequences()[0].getCanvases(), + ).groupings() } canvasIndex={1} classes={{}} diff --git a/__tests__/src/components/WindowSideBarAnnotationsPanel.test.js b/__tests__/src/components/WindowSideBarAnnotationsPanel.test.js index 10d8e32c09bb8e37b2db55d8a7d17a5a21fd6472..0ed16619527ba0b1cda0bb35ee209ccd60fefaf1 100644 --- a/__tests__/src/components/WindowSideBarAnnotationsPanel.test.js +++ b/__tests__/src/components/WindowSideBarAnnotationsPanel.test.js @@ -44,10 +44,7 @@ describe('WindowSideBarAnnotationsPanel', () => { it('renders a CanvasAnnotations for every selected canvas', () => { wrapper = createWrapper({ - selectedCanvases: [ - { id: 'abc', index: 0 }, - { id: 'xyz', index: 1 }, - ], + canvasIds: ['abc', 'xyz'], }); expect(wrapper.find(CanvasAnnotations).length).toBe(2); diff --git a/__tests__/src/components/WindowSideBarInfoPanel.test.js b/__tests__/src/components/WindowSideBarInfoPanel.test.js index 9b8ee70dff2644bec4b158cbe4f47f46fc953035..a986abb29bed4df5bdfc8941d9be18fe28d238b2 100644 --- a/__tests__/src/components/WindowSideBarInfoPanel.test.js +++ b/__tests__/src/components/WindowSideBarInfoPanel.test.js @@ -34,7 +34,7 @@ describe('WindowSideBarInfoPanel', () => { }); it('renders the canvas elements', () => { - wrapper = createWrapper({ selectedCanvases: [{ id: '1' }, { id: '2' }] }); + wrapper = createWrapper({ canvasIds: ['1', '2'] }); expect(wrapper.find(CanvasInfo).length).toBe(2); let canvasInfo = wrapper.find(CanvasInfo).at(0); diff --git a/__tests__/src/selectors/viewer.test.js b/__tests__/src/selectors/viewer.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ece25446b81a8393bf996b80cf51eb49e2d0e523 --- /dev/null +++ b/__tests__/src/selectors/viewer.test.js @@ -0,0 +1,38 @@ +import { + getCurrentCanvasWorld, +} from '../../../src/state/selectors/viewer'; + +describe('getCurrentCanvasWorld', () => { + it('returns a CanvasWorld', () => { + const windowId = 'id'; + const state = { + manifests: { + a: { + json: { + '@context': 'http://iiif.io/api/presentation/3/context.json', + id: 'whatever', + items: [ + { '@id': 'a1', type: 'Canvas' }, + { '@id': 'a2', type: 'Canvas' }, + ], + type: 'Manifest', + viewingDirection: 'sideways', + }, + }, + }, + windows: { + [windowId]: { + manifestId: 'a', + visibleCanvases: [ + 'a1', 'a2', + ], + }, + }, + }; + + const actual = getCurrentCanvasWorld(state, { windowId }); + expect(actual.canvases.length).toEqual(2); + expect(Object.keys(actual.layers).length).toEqual(2); + expect(actual.viewingDirection).toEqual('sideways'); + }); +}); diff --git a/src/components/CanvasLayers.js b/src/components/CanvasLayers.js index 7024334c49909bb7b1f6b92c6d4e9b3264c413ec..d2eb7d44d8e1aa471a211b3fc14955006f290e14 100644 --- a/src/components/CanvasLayers.js +++ b/src/components/CanvasLayers.js @@ -51,7 +51,7 @@ export class CanvasLayers extends Component { /** */ onDragEnd(result) { const { - canvas, layers, updateLayers, windowId, + canvasId, layers, updateLayers, windowId, } = this.props; if (!result.destination) return; if (result.destination.droppableId !== this.droppableId) return; @@ -68,26 +68,26 @@ export class CanvasLayers extends Component { return acc; }, {}); - updateLayers(windowId, canvas.id, payload); + updateLayers(windowId, canvasId, payload); } /** */ setLayerVisibility(layerId, value) { const { - canvas, updateLayers, windowId, + canvasId, updateLayers, windowId, } = this.props; const payload = { [layerId]: { visibility: value }, }; - updateLayers(windowId, canvas.id, payload); + updateLayers(windowId, canvasId, payload); } /** */ moveToTop(layerId) { const { - canvas, layers, updateLayers, windowId, + canvasId, layers, updateLayers, windowId, } = this.props; const sortedLayers = reorder(layers.map(l => l.id), layers.findIndex(l => l.id === layerId), 0); @@ -97,20 +97,20 @@ export class CanvasLayers extends Component { return acc; }, {}); - updateLayers(windowId, canvas.id, payload); + updateLayers(windowId, canvasId, payload); } /** */ handleOpacityChange(layerId, value) { const { - canvas, updateLayers, windowId, + canvasId, updateLayers, windowId, } = this.props; const payload = { [layerId]: { opacity: value / 100.0 }, }; - updateLayers(windowId, canvas.id, payload); + updateLayers(windowId, canvasId, payload); } /** @private */ @@ -261,9 +261,7 @@ export class CanvasLayers extends Component { } CanvasLayers.propTypes = { - canvas: PropTypes.shape({ - id: PropTypes.string, - }).isRequired, + canvasId: PropTypes.string.isRequired, classes: PropTypes.objectOf(PropTypes.string), index: PropTypes.number.isRequired, label: PropTypes.string.isRequired, diff --git a/src/components/IIIFThumbnail.js b/src/components/IIIFThumbnail.js index bd43f70ac2d2841708deab9325783632c7442afc..789c1499bb7be76aedc4c36f28feaf09ad8d255f 100644 --- a/src/components/IIIFThumbnail.js +++ b/src/components/IIIFThumbnail.js @@ -4,11 +4,21 @@ import 'intersection-observer'; // polyfill needed for Safari import Typography from '@material-ui/core/Typography'; import IntersectionObserver from '@researchgate/react-intersection-observer'; import classNames from 'classnames'; +import getThumbnail from '../lib/ThumbnailFactory'; /** * Uses InteractionObserver to "lazy" load canvas thumbnails that are in view. */ export class IIIFThumbnail extends Component { + /** */ + static getUseableLabel(resource, index) { + return (resource + && resource.getLabel + && resource.getLabel().length > 0) + ? resource.getLabel().map(label => label.value)[0] + : String(index + 1); + } + /** */ constructor(props) { @@ -17,14 +27,33 @@ export class IIIFThumbnail extends Component { this.handleIntersection = this.handleIntersection.bind(this); } + /** */ + componentDidMount() { + this.setState(state => ({ ...state, image: this.image() })); + } + + /** */ + componentDidUpdate(prevProps) { + const { maxHeight, maxWidth, resource } = this.props; + + if ( + prevProps.maxHeight !== maxHeight + || prevProps.maxWidth !== maxWidth + || prevProps.resource !== resource) { + this.setState(state => ({ ...state, image: this.image() })); // eslint-disable-line + } + } + /** * */ imageStyles() { const { - maxHeight, maxWidth, style, image, + maxHeight, maxWidth, style, } = this.props; + const image = this.image(); + const styleProps = { height: 'auto', width: 'auto' }; if (!image) return { ...style, height: maxHeight || 'auto', width: maxWidth || 'auto' }; @@ -74,9 +103,29 @@ export class IIIFThumbnail extends Component { if (loaded || !event.isIntersecting) return; - this.setState({ - loaded: true, - }); + this.setState(state => ({ ...state, loaded: true })); + } + + /** */ + image() { + const { + thumbnail, resource, maxHeight, maxWidth, + } = this.props; + + if (thumbnail) return thumbnail; + + const image = getThumbnail(resource, { maxHeight, maxWidth }); + + if (image && image.url) return image; + + return undefined; + } + + /** */ + label() { + const { label, resource } = this.props; + + return label || IIIFThumbnail.getUseableLabel(resource); } /** @@ -85,13 +134,15 @@ export class IIIFThumbnail extends Component { const { children, classes, - image, - label, + imagePlaceholder, labelled, + thumbnail, variant, } = this.props; - const { loaded } = this.state; + const { image, loaded } = this.state; + + const { url: src = imagePlaceholder } = (loaded && (thumbnail || image)) || {}; return ( <div className={classNames(classes.root, { [classes[`${variant}Root`]]: variant })}> @@ -99,15 +150,15 @@ export class IIIFThumbnail extends Component { <img alt="" role="presentation" - src={(loaded && image && image.url) || IIIFThumbnail.defaultImgPlaceholder} + src={src} style={this.imageStyles()} className={classes.image} /> </IntersectionObserver> - { labelled && label && ( + { labelled && ( <div className={classNames(classes.label, { [classes[`${variant}Label`]]: variant })}> <Typography variant="caption" classes={{ root: classNames(classes.caption, { [classes[`${variant}Caption`]]: variant }) }}> - {label} + {this.label()} </Typography> </div> )} @@ -117,33 +168,34 @@ export class IIIFThumbnail extends Component { } } -// Transparent "gray" -IIIFThumbnail.defaultImgPlaceholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mMMDQmtBwADgwF/Op8FmAAAAABJRU5ErkJggg=='; - IIIFThumbnail.propTypes = { children: PropTypes.node, classes: PropTypes.objectOf(PropTypes.string), - image: PropTypes.shape({ - height: PropTypes.number, - url: PropTypes.string.isRequired, - width: PropTypes.number, - }), + imagePlaceholder: PropTypes.string, label: PropTypes.string, labelled: PropTypes.bool, maxHeight: PropTypes.number, maxWidth: PropTypes.number, + resource: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types style: PropTypes.object, // eslint-disable-line react/forbid-prop-types + thumbnail: PropTypes.shape({ + height: PropTypes.number, + url: PropTypes.string.isRequired, + width: PropTypes.number, + }), variant: PropTypes.oneOf(['inside', 'outside']), }; IIIFThumbnail.defaultProps = { children: null, classes: {}, - image: null, + // Transparent "gray" + imagePlaceholder: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mMMDQmtBwADgwF/Op8FmAAAAABJRU5ErkJggg==', label: undefined, labelled: false, maxHeight: null, maxWidth: null, style: {}, + thumbnail: null, variant: null, }; diff --git a/src/components/LayersPanel.js b/src/components/LayersPanel.js index 4ef013f034e128e2614a91ce603c4d0654c6a392..09cda41e93b4ed1a75c3b9f29db94ca0c97157fe 100644 --- a/src/components/LayersPanel.js +++ b/src/components/LayersPanel.js @@ -12,7 +12,7 @@ export class LayersPanel extends Component { */ render() { const { - canvases, + canvasIds, id, t, windowId, @@ -24,12 +24,12 @@ export class LayersPanel extends Component { id={id} windowId={windowId} > - {canvases.map((canvas, index) => ( + {canvasIds.map((canvasId, index) => ( <CanvasLayers - canvasId={canvas.id} + canvasId={canvasId} index={index} - key={canvas.id} - totalSize={canvases.length} + key={canvasId} + totalSize={canvasIds.length} windowId={windowId} /> ))} @@ -39,14 +39,12 @@ export class LayersPanel extends Component { } LayersPanel.propTypes = { - canvases: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string, - })), + canvasIds: PropTypes.arrayOf(PropTypes.string), id: PropTypes.string.isRequired, t: PropTypes.func.isRequired, windowId: PropTypes.string.isRequired, }; LayersPanel.defaultProps = { - canvases: [], + canvasIds: [], }; diff --git a/src/components/SidebarIndexItem.js b/src/components/SidebarIndexItem.js index bad724843d4a6fbb43ba38e419b8a3fdf51c1616..909b218a95b8fe3d3604ddb4d7105b89f4fbfe2b 100644 --- a/src/components/SidebarIndexItem.js +++ b/src/components/SidebarIndexItem.js @@ -8,7 +8,7 @@ export class SidebarIndexItem extends Component { /** */ render() { const { - classes, canvas, + classes, label, } = this.props; return ( @@ -17,7 +17,7 @@ export class SidebarIndexItem extends Component { className={classNames(classes.label)} variant="body1" > - {canvas.label} + {label} </Typography> </> ); @@ -25,6 +25,6 @@ export class SidebarIndexItem extends Component { } SidebarIndexItem.propTypes = { - canvas: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types classes: PropTypes.objectOf(PropTypes.string).isRequired, + label: PropTypes.string.isRequired, }; diff --git a/src/components/SidebarIndexList.js b/src/components/SidebarIndexList.js index 0972d74abaf7227379cd7568afe48653776d3a9d..667b2764fc930c5dda597616a8f77213bee0d70d 100644 --- a/src/components/SidebarIndexList.js +++ b/src/components/SidebarIndexList.js @@ -25,7 +25,7 @@ export class SidebarIndexList extends Component { canvases, classes, containerRef, - selectedCanvases, + selectedCanvasIds, setCanvas, variant, windowId, @@ -56,15 +56,15 @@ export class SidebarIndexList extends Component { onClick={onClick} button component="li" - selected={!!selectedCanvases.find(c => c.id === canvas.id)} + selected={selectedCanvasIds.includes(canvas.id)} > <ScrollTo containerRef={containerRef} key={`${canvas.id}-${variant}`} offsetTop={96} // offset for the height of the form above - scrollTo={!!selectedCanvases.find(c => c.id === canvas.id)} + scrollTo={selectedCanvasIds.includes(canvas.id)} > - <Item canvas={canvas} otherCanvas={canvases[canvasIndex]} /> + <Item label={canvas.label} canvas={canvases[canvasIndex]} /> </ScrollTo> </MenuItem> ); @@ -79,13 +79,13 @@ SidebarIndexList.propTypes = { canvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types classes: PropTypes.objectOf(PropTypes.string).isRequired, containerRef: PropTypes.oneOf([PropTypes.func, PropTypes.object]).isRequired, - selectedCanvases: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string })), + selectedCanvasIds: PropTypes.arrayOf(PropTypes.string), setCanvas: PropTypes.func.isRequired, variant: PropTypes.oneOf(['item', 'thumbnail']), windowId: PropTypes.string.isRequired, }; SidebarIndexList.defaultProps = { - selectedCanvases: [], + selectedCanvasIds: [], variant: 'item', }; diff --git a/src/components/SidebarIndexThumbnail.js b/src/components/SidebarIndexThumbnail.js index c2ab64128277c054228e349045a06de5299faf50..5ac21782e7c5403d3259abf863246def01c510a7 100644 --- a/src/components/SidebarIndexThumbnail.js +++ b/src/components/SidebarIndexThumbnail.js @@ -9,14 +9,15 @@ export class SidebarIndexThumbnail extends Component { /** */ render() { const { - classes, otherCanvas, canvas, height, width, + classes, canvas, height, label, width, } = this.props; return ( <> <div style={{ minWidth: 50 }}> <IIIFThumbnail - resource={otherCanvas} + label={label} + resource={canvas} className={classNames(classes.clickable)} maxHeight={height} maxWidth={width} @@ -26,7 +27,7 @@ export class SidebarIndexThumbnail extends Component { className={classNames(classes.label)} variant="body1" > - {canvas.label} + {label} </Typography> </> ); @@ -37,7 +38,7 @@ SidebarIndexThumbnail.propTypes = { canvas: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types classes: PropTypes.objectOf(PropTypes.string).isRequired, height: PropTypes.number, - otherCanvas: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + label: PropTypes.string.isRequired, width: PropTypes.number, }; diff --git a/src/components/ThumbnailCanvasGrouping.js b/src/components/ThumbnailCanvasGrouping.js index 958e2e5f2a4d10e31a48a932f5b4f878ec06da50..cc966ff815eba1093a823238a750ce70f5ac309c 100644 --- a/src/components/ThumbnailCanvasGrouping.js +++ b/src/components/ThumbnailCanvasGrouping.js @@ -36,7 +36,7 @@ export class ThumbnailCanvasGrouping extends PureComponent { const { canvasGroupings, position, height, } = data; - const currentGroupings = canvasGroupings.groupings()[index]; + const currentGroupings = canvasGroupings[index]; const SPACING = 8; return ( <div diff --git a/src/components/ThumbnailNavigation.js b/src/components/ThumbnailNavigation.js index 48accae8e21f7708b802ab5d503cea106392a211..252e458243868b6f5b6c6ece55639dd510548595 100644 --- a/src/components/ThumbnailNavigation.js +++ b/src/components/ThumbnailNavigation.js @@ -48,7 +48,7 @@ export class ThumbnailNavigation extends Component { */ calculateScaledSize(index) { const { thumbnailNavigation, canvasGroupings, position } = this.props; - const canvases = canvasGroupings.groupings()[index]; + const canvases = canvasGroupings[index]; const world = new CanvasWorld(canvases); const bounds = world.worldBounds(); switch (position) { @@ -125,7 +125,7 @@ export class ThumbnailNavigation extends Component { /** */ itemCount() { const { canvasGroupings } = this.props; - return canvasGroupings.groupings().length; + return canvasGroupings.length; } /** */ @@ -231,7 +231,7 @@ export class ThumbnailNavigation extends Component { } ThumbnailNavigation.propTypes = { - canvasGroupings: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + canvasGroupings: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types canvasIndex: PropTypes.number.isRequired, classes: PropTypes.objectOf(PropTypes.string).isRequired, hasNextCanvas: PropTypes.bool, diff --git a/src/components/WindowSideBarAnnotationsPanel.js b/src/components/WindowSideBarAnnotationsPanel.js index 6cf9ac8bf0bdea6f1f25d0cd8bfd09e4bc7e1c41..06068abe31e400d2b9500238310850fdc64962ea 100644 --- a/src/components/WindowSideBarAnnotationsPanel.js +++ b/src/components/WindowSideBarAnnotationsPanel.js @@ -15,7 +15,7 @@ export class WindowSideBarAnnotationsPanel extends Component { */ render() { const { - annotationCount, classes, selectedCanvases, t, windowId, id, + annotationCount, classes, canvasIds, t, windowId, id, } = this.props; return ( <CompanionWindow @@ -29,12 +29,12 @@ export class WindowSideBarAnnotationsPanel extends Component { <Typography component="p" variant="subtitle2">{t('showingNumAnnotations', { number: annotationCount })}</Typography> </div> - {selectedCanvases.map((canvas, index) => ( + {canvasIds.map((canvasId, index) => ( <CanvasAnnotations - canvasId={canvas.id} - key={canvas.id} + canvasId={canvasId} + key={canvasId} index={index} - totalSize={selectedCanvases.length} + totalSize={canvasIds.length} windowId={windowId} /> ))} @@ -45,15 +45,14 @@ export class WindowSideBarAnnotationsPanel extends Component { WindowSideBarAnnotationsPanel.propTypes = { annotationCount: PropTypes.number.isRequired, + canvasIds: PropTypes.arrayOf(PropTypes.string), classes: PropTypes.objectOf(PropTypes.string).isRequired, - id: PropTypes.string.isRequired, - selectedCanvases: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string })), t: PropTypes.func, windowId: PropTypes.string.isRequired, }; WindowSideBarAnnotationsPanel.defaultProps = { - selectedCanvases: [], + canvasIds: [], t: key => key, }; diff --git a/src/components/WindowSideBarInfoPanel.js b/src/components/WindowSideBarInfoPanel.js index 176e0761c564c214b4f95cbd43a309a5d380490a..8136fc66abd662197443db92568866885a0e5305 100644 --- a/src/components/WindowSideBarInfoPanel.js +++ b/src/components/WindowSideBarInfoPanel.js @@ -20,11 +20,11 @@ export class WindowSideBarInfoPanel extends Component { const { windowId, id, + canvasIds, classes, collectionPath, t, locale, - selectedCanvases, setLocale, availableLocales, showLocalePicker, @@ -48,13 +48,13 @@ export class WindowSideBarInfoPanel extends Component { )} > { - selectedCanvases.map((canvas, index) => ( - <div key={canvas.id} className={classes.section}> + canvasIds.map((canvasId, index) => ( + <div key={canvasId} className={classes.section}> <CanvasInfo id={id} - canvasId={canvas.id} + canvasId={canvasId} index={index} - totalSize={selectedCanvases.length} + totalSize={canvasIds.length} windowId={windowId} /> </div> @@ -80,11 +80,11 @@ export class WindowSideBarInfoPanel extends Component { WindowSideBarInfoPanel.propTypes = { availableLocales: PropTypes.arrayOf(PropTypes.string), + canvasIds: PropTypes.arrayOf(PropTypes.string), classes: PropTypes.objectOf(PropTypes.string), collectionPath: PropTypes.arrayOf(PropTypes.string), id: PropTypes.string.isRequired, locale: PropTypes.string, - selectedCanvases: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string })), setLocale: PropTypes.func, showLocalePicker: PropTypes.bool, t: PropTypes.func, @@ -93,10 +93,10 @@ WindowSideBarInfoPanel.propTypes = { WindowSideBarInfoPanel.defaultProps = { availableLocales: [], + canvasIds: [], classes: {}, collectionPath: [], locale: '', - selectedCanvases: [], setLocale: undefined, showLocalePicker: false, t: key => key, diff --git a/src/containers/AnnotationsOverlay.js b/src/containers/AnnotationsOverlay.js index 43be6e15931dddb888d88d183ac1805caea27df5..8619925686bfcb71677d7d68e7d65ad71e4d89d6 100644 --- a/src/containers/AnnotationsOverlay.js +++ b/src/containers/AnnotationsOverlay.js @@ -4,18 +4,15 @@ import { withTranslation } from 'react-i18next'; import { withPlugins } from '../extend/withPlugins'; import { AnnotationsOverlay } from '../components/AnnotationsOverlay'; import * as actions from '../state/actions'; -import CanvasWorld from '../lib/CanvasWorld'; import { getWindow, - getSequenceViewingDirection, - getLayersForVisibleCanvases, - getVisibleCanvases, getSearchAnnotationsForWindow, getCompanionWindowsForContent, getTheme, getConfig, getPresentAnnotationsOnSelectedCanvases, getSelectedAnnotationId, + getCurrentCanvasWorld, } from '../state/selectors'; /** @@ -25,11 +22,7 @@ import { */ const mapStateToProps = (state, { windowId }) => ({ annotations: getPresentAnnotationsOnSelectedCanvases(state, { windowId }), - canvasWorld: new CanvasWorld( - getVisibleCanvases(state, { windowId }), - getLayersForVisibleCanvases(state, { windowId }), - getSequenceViewingDirection(state, { windowId }), - ), + canvasWorld: getCurrentCanvasWorld(state, { windowId }), drawAnnotations: getConfig(state).window.forceDrawAnnotations || getCompanionWindowsForContent(state, { content: 'annotations', windowId }).length > 0, drawSearchAnnotations: getConfig(state).window.forceDrawAnnotations || getCompanionWindowsForContent(state, { content: 'search', windowId }).length > 0, highlightAllAnnotations: getWindow(state, { windowId }).highlightAllAnnotations, diff --git a/src/containers/CanvasLayers.js b/src/containers/CanvasLayers.js index 7e36dc1d6f52f381037ae086f9cb9df7226c773d..aba5f5c7c7703a0f989801e99022498742ca27cf 100644 --- a/src/containers/CanvasLayers.js +++ b/src/containers/CanvasLayers.js @@ -4,7 +4,6 @@ import { withTranslation } from 'react-i18next'; import { withStyles } from '@material-ui/core/styles'; import * as actions from '../state/actions'; import { - getCanvas, getCanvasLabel, getLayers, getSortedLayers, @@ -13,11 +12,7 @@ import { CanvasLayers } from '../components/CanvasLayers'; /** For connect */ const mapStateToProps = (state, { canvasId, windowId }) => ({ - canvas: getCanvas(state, { canvasId, windowId }), - label: getCanvasLabel(state, { - canvasId, - windowId, - }), + label: getCanvasLabel(state, { canvasId, windowId }), layerMetadata: getLayers(state, { canvasId, windowId }), layers: getSortedLayers(state, { canvasId, windowId }), }); diff --git a/src/containers/IIIFThumbnail.js b/src/containers/IIIFThumbnail.js index 706e920b84b9f4441eedf45dc49fdaec4b22e9d4..3caffe62d464c24ad6d14306d0ab8e8892cd473c 100644 --- a/src/containers/IIIFThumbnail.js +++ b/src/containers/IIIFThumbnail.js @@ -1,25 +1,8 @@ import { compose } from 'redux'; -import { connect } from 'react-redux'; import { withTranslation } from 'react-i18next'; import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; import { IIIFThumbnail } from '../components/IIIFThumbnail'; -import getThumbnail from '../lib/ThumbnailFactory'; - -/** */ -function getLabel(resource) { - return resource.getLabel().length > 0 - ? resource.getLabel().map(label => label.value)[0] - : String(resource.index + 1); -} - -/** */ -const mapStateToProps = (state, { - label, labelled, maxHeight, maxWidth, resource, thumbnail, -}) => ({ - image: thumbnail || getThumbnail(resource, { maxHeight, maxWidth }), - label: labelled && (label || getLabel(resource)), -}); /** * Styles for withStyles HOC @@ -65,7 +48,6 @@ const styles = theme => ({ const enhance = compose( withStyles(styles), withTranslation(), - connect(mapStateToProps), withPlugins('IIIFThumbnail'), ); diff --git a/src/containers/LayersPanel.js b/src/containers/LayersPanel.js index b438a5a6af4c725a5958bb1eb4cae122bd278cf6..16a1a9b01b5cec0d1005c357debb1fd72dcf801d 100644 --- a/src/containers/LayersPanel.js +++ b/src/containers/LayersPanel.js @@ -5,14 +5,14 @@ import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; import { LayersPanel } from '../components/LayersPanel'; import { - getVisibleCanvases, + getVisibleCanvasIds, } from '../state/selectors'; /** * mapStateToProps - to hook up connect */ const mapStateToProps = (state, { id, windowId }) => ({ - canvases: getVisibleCanvases(state, { windowId }), + canvasIds: getVisibleCanvasIds(state, { windowId }), }); /** diff --git a/src/containers/OpenSeadragonViewer.js b/src/containers/OpenSeadragonViewer.js index 253c10ee6b5a3b902181edca81af6b7b2740f302..bf5d3fef6b091af9709e994c6e6a49d2d206b1f3 100644 --- a/src/containers/OpenSeadragonViewer.js +++ b/src/containers/OpenSeadragonViewer.js @@ -6,18 +6,15 @@ import flatten from 'lodash/flatten'; import { withPlugins } from '../extend/withPlugins'; import { OpenSeadragonViewer } from '../components/OpenSeadragonViewer'; import * as actions from '../state/actions'; -import CanvasWorld from '../lib/CanvasWorld'; import { getVisibleCanvasNonTiledResources, getCurrentCanvas, getCanvasLabel, - getSequenceViewingDirection, - getLayersForVisibleCanvases, - getVisibleCanvases, getViewer, getConfig, getCompanionWindowsForContent, selectInfoResponses, + getCurrentCanvasWorld, } from '../state/selectors'; /** @@ -26,12 +23,7 @@ import { * @private */ const mapStateToProps = (state, { windowId }) => { - const canvasWorld = new CanvasWorld( - getVisibleCanvases(state, { windowId }), - getLayersForVisibleCanvases(state, { windowId }), - getSequenceViewingDirection(state, { windowId }), - ); - + const canvasWorld = getCurrentCanvasWorld(state, { windowId }); const infoResponses = selectInfoResponses(state); const imageServiceIds = flatten(canvasWorld.canvases.map(c => c.imageServiceIds)); diff --git a/src/containers/SidebarIndexList.js b/src/containers/SidebarIndexList.js index 053de13c60219b02d3b25bc0b51542c36c60b34f..866e7d61d7229e239182378c8cd3e075a5a85cfa 100644 --- a/src/containers/SidebarIndexList.js +++ b/src/containers/SidebarIndexList.js @@ -7,21 +7,18 @@ import * as actions from '../state/actions'; import { getCompanionWindow, getCanvases, - getVisibleCanvases, + getVisibleCanvasIds, } from '../state/selectors'; import { SidebarIndexList } from '../components/SidebarIndexList'; /** * mapStateToProps - to hook up connect */ -const mapStateToProps = (state, { id, windowId }) => { - const canvases = getCanvases(state, { windowId }); - return { - canvases, - selectedCanvases: getVisibleCanvases(state, { windowId }), - variant: getCompanionWindow(state, { companionWindowId: id, windowId }).variant, - }; -}; +const mapStateToProps = (state, { id, windowId }) => ({ + canvases: getCanvases(state, { windowId }), + selectedCanvasIds: getVisibleCanvasIds(state, { windowId }), + variant: getCompanionWindow(state, { companionWindowId: id, windowId }).variant, +}); /** * mapStateToProps - used to hook up connect to state diff --git a/src/containers/ThumbnailNavigation.js b/src/containers/ThumbnailNavigation.js index eb82793df90cd4a35c6432dfe4b9a59d339bd6cb..e3be9a8f3363f1068771976c0d71929445d9e771 100644 --- a/src/containers/ThumbnailNavigation.js +++ b/src/containers/ThumbnailNavigation.js @@ -3,13 +3,12 @@ import { connect } from 'react-redux'; import { withTranslation } from 'react-i18next'; import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; -import CanvasGroupings from '../lib/CanvasGroupings'; import * as actions from '../state/actions'; import { ThumbnailNavigation } from '../components/ThumbnailNavigation'; import { getCompanionWindow, getWindow, getNextCanvasGrouping, getPreviousCanvasGrouping, - getCanvases, getCanvasIndex, getWindowViewType, + getCanvasGroupings, getCanvasIndex, getWindowViewType, getSequenceViewingDirection, getConfig, } from '../state/selectors'; @@ -18,24 +17,18 @@ import { * @memberof ThumbnailNavigation * @private */ -const mapStateToProps = (state, { windowId }) => { - const viewType = getWindowViewType(state, { windowId }); - return { - canvasGroupings: new CanvasGroupings( - getCanvases(state, { windowId }), - viewType, - ), - canvasIndex: getCanvasIndex(state, { windowId }), - hasNextCanvas: !!getNextCanvasGrouping(state, { windowId }), - hasPreviousCanvas: !!getPreviousCanvasGrouping(state, { windowId }), - position: getCompanionWindow(state, { - companionWindowId: getWindow(state, { windowId }).thumbnailNavigationId, - }).position, - thumbnailNavigation: getConfig(state).thumbnailNavigation, - view: viewType, - viewingDirection: getSequenceViewingDirection(state, { windowId }), - }; -}; +const mapStateToProps = (state, { windowId }) => ({ + canvasGroupings: getCanvasGroupings(state, { windowId }), + canvasIndex: getCanvasIndex(state, { windowId }), + hasNextCanvas: !!getNextCanvasGrouping(state, { windowId }), + hasPreviousCanvas: !!getPreviousCanvasGrouping(state, { windowId }), + position: getCompanionWindow(state, { + companionWindowId: getWindow(state, { windowId }).thumbnailNavigationId, + }).position, + thumbnailNavigation: getConfig(state).thumbnailNavigation, + view: getWindowViewType(state, { windowId }), + viewingDirection: getSequenceViewingDirection(state, { windowId }), +}); /** * mapDispatchToProps - used to hook up connect to action creators diff --git a/src/containers/WindowSideBarAnnotationsPanel.js b/src/containers/WindowSideBarAnnotationsPanel.js index 854f5175882ed828f177d762ef796abd6a4f7bab..904cea2f93053bcbca8d2c0903931f934ccc6e9d 100644 --- a/src/containers/WindowSideBarAnnotationsPanel.js +++ b/src/containers/WindowSideBarAnnotationsPanel.js @@ -4,7 +4,7 @@ import { withTranslation } from 'react-i18next'; import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; import { - getVisibleCanvases, + getVisibleCanvasIds, getAnnotationResourcesByMotivation, } from '../state/selectors'; import { WindowSideBarAnnotationsPanel } from '../components/WindowSideBarAnnotationsPanel'; @@ -19,7 +19,7 @@ const mapStateToProps = (state, { windowId }) => ({ state, { windowId }, ).length, - selectedCanvases: getVisibleCanvases(state, { windowId }), + canvasIds: getVisibleCanvasIds(state, { windowId }), }); /** */ diff --git a/src/containers/WindowSideBarCanvasPanel.js b/src/containers/WindowSideBarCanvasPanel.js index a55744b1cb0b8b51008831ce1b70ede2629642e3..e26303a543965736c65bfbfb8cfe203d5f466cad 100644 --- a/src/containers/WindowSideBarCanvasPanel.js +++ b/src/containers/WindowSideBarCanvasPanel.js @@ -8,8 +8,6 @@ import { WindowSideBarCanvasPanel } from '../components/WindowSideBarCanvasPanel import { getCompanionWindow, getDefaultSidebarVariant, - getCanvases, - getVisibleCanvases, getSequenceTreeStructure, getWindow, getManifestoInstance, @@ -19,7 +17,6 @@ import { * mapStateToProps - to hook up connect */ const mapStateToProps = (state, { id, windowId }) => { - const canvases = getCanvases(state, { windowId }); const treeStructure = getSequenceTreeStructure(state, { windowId }); const window = getWindow(state, { windowId }); const { config } = state; @@ -27,10 +24,8 @@ const mapStateToProps = (state, { id, windowId }) => { const collectionPath = window.collectionPath || []; const collectionId = collectionPath && collectionPath[collectionPath.length - 1]; return { - canvases, collection: collectionId && getManifestoInstance(state, { manifestId: collectionId }), config, - selectedCanvases: getVisibleCanvases(state, { windowId }), showToc: treeStructure && treeStructure.nodes && treeStructure.nodes.length > 0, variant: companionWindow.variant || getDefaultSidebarVariant(state, { windowId }), diff --git a/src/containers/WindowSideBarInfoPanel.js b/src/containers/WindowSideBarInfoPanel.js index 0a69e62d0ebe70f5cc9079aea63e48c2725b1c94..9fc498ce117521c64e176a2559bebd8c8b40692d 100644 --- a/src/containers/WindowSideBarInfoPanel.js +++ b/src/containers/WindowSideBarInfoPanel.js @@ -8,7 +8,7 @@ import { getCompanionWindow, getManifestLocale, getMetadataLocales, - getVisibleCanvases, + getVisibleCanvasIds, getWindowConfig, getWindow, } from '../state/selectors'; @@ -21,10 +21,10 @@ import { WindowSideBarInfoPanel } from '../components/WindowSideBarInfoPanel'; */ const mapStateToProps = (state, { id, windowId }) => ({ availableLocales: getMetadataLocales(state, { companionWindowId: id, windowId }), + canvasIds: getVisibleCanvasIds(state, { windowId }), collectionPath: (getWindow(state, { windowId }) || {}).collectionPath, locale: getCompanionWindow(state, { companionWindowId: id }).locale || getManifestLocale(state, { windowId }), - selectedCanvases: getVisibleCanvases(state, { windowId }), showLocalePicker: getWindowConfig(state, { windowId }).showLocalePicker, }); diff --git a/src/state/selectors/canvases.js b/src/state/selectors/canvases.js index 684676bcd8fb15756f7a5246b2c6b535ef12046c..4a3c9924889b25d1caa071bc965ae2a557d018a8 100644 --- a/src/state/selectors/canvases.js +++ b/src/state/selectors/canvases.js @@ -70,7 +70,7 @@ export const getVisibleCanvases = createSelector( * @param {string} props.windowId * @return {Array} */ -const getCanvasGroupings = createSelector( +export const getCanvasGroupings = createSelector( [ getCanvases, getWindowViewType, diff --git a/src/state/selectors/index.js b/src/state/selectors/index.js index 866141957ffc7875f157663ae457c88428e38c5b..bad2660352779524c7324cf25d3f33f32ec1cef9 100644 --- a/src/state/selectors/index.js +++ b/src/state/selectors/index.js @@ -12,3 +12,4 @@ export * from './layers'; export * from './sequences'; export * from './auth'; export * from './utils'; +export * from './viewer'; diff --git a/src/state/selectors/viewer.js b/src/state/selectors/viewer.js new file mode 100644 index 0000000000000000000000000000000000000000..27b5992553d5d5f2d739f35d2f863c90d46e99d7 --- /dev/null +++ b/src/state/selectors/viewer.js @@ -0,0 +1,14 @@ +import { createSelector } from 'reselect'; +import CanvasWorld from '../../lib/CanvasWorld'; + +import { getVisibleCanvases } from './canvases'; +import { getLayersForVisibleCanvases } from './layers'; +import { getSequenceViewingDirection } from './sequences'; + +/** Instantiate a manifesto instance */ +export const getCurrentCanvasWorld = createSelector( + getVisibleCanvases, + getLayersForVisibleCanvases, + getSequenceViewingDirection, + (canvases, layers, viewingDirection) => new CanvasWorld(canvases, layers, viewingDirection), +);