Skip to content
Snippets Groups Projects
Commit c7d4f3ec authored by Chris Beer's avatar Chris Beer
Browse files

Refactor WindowViewer to use the getSelectedCanvases selector

parent 1530917a
No related branches found
No related tags found
No related merge requests found
......@@ -4,6 +4,7 @@ import OpenSeadragon from 'openseadragon';
import { OpenSeadragonViewer } from '../../../src/components/OpenSeadragonViewer';
import OpenSeadragonCanvasOverlay from '../../../src/lib/OpenSeadragonCanvasOverlay';
import Annotation from '../../../src/lib/Annotation';
import CanvasWorld from '../../../src/lib/CanvasWorld';
jest.mock('openseadragon');
jest.mock('../../../src/lib/OpenSeadragonCanvasOverlay');
......@@ -34,6 +35,7 @@ describe('OpenSeadragonViewer', () => {
updateViewport={updateViewport}
t={k => k}
classes={{ controls: 'controls' }}
canvasWorld={new CanvasWorld([])}
>
<div className="foo" />
</OpenSeadragonViewer>,
......@@ -108,6 +110,7 @@ describe('OpenSeadragonViewer', () => {
viewer={{ x: 1, y: 0, zoom: 0.5 }}
config={{}}
updateViewport={updateViewport}
canvasWorld={new CanvasWorld([])}
t={k => k}
>
<div className="foo" />
......
......@@ -8,7 +8,7 @@ import fixture from '../../fixtures/version-2/019.json';
import emptyCanvasFixture from '../../fixtures/version-2/emptyCanvas.json';
import otherContentFixture from '../../fixtures/version-2/299843.json';
let canvases = manifesto.create(fixture).getSequences()[0].getCanvases();
let currentCanvases = manifesto.create(fixture).getSequences()[0].getCanvases();
let mockWindow = {
canvasIndex: 0,
......@@ -23,7 +23,7 @@ function createWrapper(props) {
infoResponses={{}}
fetchInfoResponse={() => {}}
fetchAnnotation={() => {}}
canvases={canvases}
currentCanvases={[currentCanvases[1]]}
window={mockWindow}
{...props}
/>,
......@@ -32,10 +32,8 @@ function createWrapper(props) {
describe('WindowViewer', () => {
let wrapper;
beforeEach(() => {
wrapper = createWrapper();
});
it('renders properly', () => {
wrapper = createWrapper();
expect(wrapper.matchesElement(
<>
<OSDViewer>
......@@ -44,31 +42,6 @@ describe('WindowViewer', () => {
</>,
)).toBe(true);
});
describe('currentCanvases', () => {
it('by default returns a single canvas', () => {
expect(wrapper.instance().currentCanvases().length).toEqual(1);
});
describe('book view', () => {
it('when the first canvas is selected', () => {
wrapper = createWrapper({
window: {
canvasIndex: 0,
view: 'book',
},
});
expect(wrapper.instance().currentCanvases().length).toEqual(1);
});
it('when the second canvas is selected', () => {
wrapper = createWrapper({
window: {
canvasIndex: 1,
view: 'book',
},
});
expect(wrapper.instance().currentCanvases().length).toEqual(2);
});
});
});
describe('currentInfoResponses', () => {
describe('returns only available infoResponses', () => {
it('isFetching is false', () => {
......@@ -131,38 +104,32 @@ describe('WindowViewer', () => {
});
});
});
it('when view type changes', () => {
expect(wrapper.instance().canvasGroupings.groupings().length).toEqual(3);
wrapper.setProps({
window: {
canvasIndex: 0,
view: 'book',
},
});
expect(wrapper.instance().canvasGroupings.groupings().length).toEqual(2);
});
describe('componentDidMount', () => {
it('does not call fetchInfoResponse for a canvas that has no images', () => {
const mockFnCanvas0 = jest.fn();
const mockFnCanvas2 = jest.fn();
canvases = manifesto.create(emptyCanvasFixture).getSequences()[0].getCanvases();
const canvases = manifesto.create(emptyCanvasFixture).getSequences()[0].getCanvases();
currentCanvases = [canvases[0]];
mockWindow = {
canvasIndex: 0,
view: 'single',
};
wrapper = createWrapper(
{
canvases,
currentCanvases,
fetchInfoResponse: mockFnCanvas0,
window: mockWindow,
},
);
expect(mockFnCanvas0).toHaveBeenCalledTimes(1);
currentCanvases = [canvases[2]];
wrapper = createWrapper(
{
canvases,
currentCanvases,
fetchInfoResponse: mockFnCanvas2,
window: { canvasIndex: 2, view: 'single' },
},
......@@ -171,9 +138,11 @@ describe('WindowViewer', () => {
});
it('calls fetchAnnotation when otherContent is present', () => {
const mockFnAnno = jest.fn();
canvases = manifesto.create(otherContentFixture).getSequences()[0].getCanvases();
const canvases = manifesto.create(otherContentFixture).getSequences()[0].getCanvases();
currentCanvases = [canvases[0]];
wrapper = createWrapper(
{ canvases, fetchAnnotation: mockFnAnno },
{ currentCanvases, fetchAnnotation: mockFnAnno },
);
expect(mockFnAnno).toHaveBeenCalledTimes(1);
});
......@@ -182,16 +151,17 @@ describe('WindowViewer', () => {
describe('componentDidUpdate', () => {
it('does not call fetchInfoResponse for a canvas that has no images', () => {
const mockFn = jest.fn();
canvases = manifesto.create(emptyCanvasFixture).getSequences()[0].getCanvases();
const canvases = manifesto.create(emptyCanvasFixture).getSequences()[0].getCanvases();
currentCanvases = [canvases[2]];
mockWindow = {
canvasIndex: 2,
view: 'single',
};
wrapper = createWrapper(
{ canvases, fetchInfoResponse: mockFn, window: mockWindow },
{ currentCanvases, fetchInfoResponse: mockFn, window: mockWindow },
);
wrapper.setProps({ window: { canvasIndex: 3, view: 'single' } });
wrapper.setProps({ currentCanvases: [canvases[3]], window: { canvasIndex: 3, view: 'single' } });
expect(mockFn).toHaveBeenCalledTimes(0);
});
......
......@@ -132,11 +132,11 @@ export class OpenSeadragonViewer extends Component {
* annotationsToContext - converts anontations to a canvas context
*/
annotationsToContext(annotations) {
const { currentCanvases } = this.props;
const { canvasWorld } = this.props;
const context = this.osdCanvasOverlay.context2d;
annotations.forEach((annotation) => {
annotation.resources.forEach((resource) => {
const offset = new CanvasWorld(currentCanvases).offsetByCanvas(resource.targetId);
const offset = canvasWorld.offsetByCanvas(resource.targetId);
const fragment = resource.fragmentSelector;
fragment[0] += offset.x;
context.strokeStyle = 'yellow';
......@@ -149,7 +149,7 @@ export class OpenSeadragonViewer extends Component {
/**
*/
addTileSource(tileSource, i = 0) {
const { currentCanvases } = this.props;
const { canvasWorld } = this.props;
return new Promise((resolve, reject) => {
if (!this.viewer) {
return;
......@@ -157,7 +157,7 @@ export class OpenSeadragonViewer extends Component {
this.viewer.addTiledImage({
error: event => reject(event),
fitBounds: new OpenSeadragon.Rect(
...new CanvasWorld(currentCanvases).canvasToWorldCoordinates(i),
...canvasWorld.canvasToWorldCoordinates(i),
),
success: event => resolve(event),
tileSource,
......@@ -197,8 +197,8 @@ export class OpenSeadragonViewer extends Component {
* zoomToWorld - zooms the viewer to the extent of the canvas world
*/
zoomToWorld(immediately = true) {
const { currentCanvases } = this.props;
this.fitBounds(...new CanvasWorld(currentCanvases).worldBounds(), immediately);
const { canvasWorld } = this.props;
this.fitBounds(...canvasWorld.worldBounds(), immediately);
}
/**
......@@ -262,7 +262,6 @@ OpenSeadragonViewer.defaultProps = {
annotations: [],
children: null,
classes: {},
currentCanvases: [],
label: null,
tileSources: [],
viewer: null,
......@@ -271,9 +270,9 @@ OpenSeadragonViewer.defaultProps = {
OpenSeadragonViewer.propTypes = {
annotations: PropTypes.arrayOf(PropTypes.object),
canvasWorld: PropTypes.instanceOf(CanvasWorld).isRequired,
children: PropTypes.element,
classes: PropTypes.object, // eslint-disable-line react/forbid-prop-types
currentCanvases: PropTypes.arrayOf(PropTypes.object),
label: PropTypes.string,
t: PropTypes.func.isRequired,
tileSources: PropTypes.arrayOf(PropTypes.object),
......
......@@ -12,7 +12,7 @@ export class WindowCanvasNavigationControls extends Component {
/** */
render() {
const {
canvases, visible, window, zoomToWorld,
visible, window, zoomToWorld,
} = this.props;
if (!visible) return (<></>);
......@@ -20,7 +20,7 @@ export class WindowCanvasNavigationControls extends Component {
return (
<div className={ns('canvas-nav')}>
<ZoomControls windowId={window.id} zoomToWorld={zoomToWorld} />
<ViewerNavigation window={window} canvases={canvases} />
<ViewerNavigation window={window} />
<ViewerInfo windowId={window.id} />
</div>
);
......@@ -29,7 +29,6 @@ export class WindowCanvasNavigationControls extends Component {
WindowCanvasNavigationControls.propTypes = {
canvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
visible: PropTypes.bool,
window: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
zoomToWorld: PropTypes.func.isRequired,
......
......@@ -3,33 +3,21 @@ import PropTypes from 'prop-types';
import OSDViewer from '../containers/OpenSeadragonViewer';
import WindowCanvasNavigationControls from '../containers/WindowCanvasNavigationControls';
import ManifestoCanvas from '../lib/ManifestoCanvas';
import CanvasGroupings from '../lib/CanvasGroupings';
/**
* Represents a WindowViewer in the mirador workspace. Responsible for mounting
* OSD and Navigation
*/
export class WindowViewer extends Component {
/**
* @param {Object} props
*/
constructor(props) {
super(props);
const { canvases, window } = this.props;
this.canvases = canvases;
this.canvasGroupings = new CanvasGroupings(this.canvases, window.view);
}
/**
* componentDidMount - React lifecycle method
* Request the initial canvas on mount
*/
componentDidMount() {
const { fetchInfoResponse, fetchAnnotation } = this.props;
const { currentCanvases, fetchInfoResponse, fetchAnnotation } = this.props;
if (!this.infoResponseIsInStore()) {
this.currentCanvases().forEach((canvas) => {
currentCanvases.forEach((canvas) => {
const manifestoCanvas = new ManifestoCanvas(canvas);
const { imageInformationUri } = manifestoCanvas;
if (imageInformationUri) {
......@@ -47,11 +35,14 @@ export class WindowViewer extends Component {
* Request a new canvas if it is needed
*/
componentDidUpdate(prevProps) {
const { window, fetchInfoResponse, fetchAnnotation } = this.props;
const {
currentCanvases, window, fetchInfoResponse, fetchAnnotation,
} = this.props;
if (prevProps.window.view !== window.view
|| (prevProps.window.canvasIndex !== window.canvasIndex && !this.infoResponseIsInStore())
) {
this.currentCanvases().forEach((canvas) => {
currentCanvases.forEach((canvas) => {
const manifestoCanvas = new ManifestoCanvas(canvas);
const { imageInformationUri } = manifestoCanvas;
if (imageInformationUri) {
......@@ -62,10 +53,6 @@ export class WindowViewer extends Component {
});
});
}
// If the view changes, create a new instance
if (prevProps.window.view !== window.view) {
this.canvasGroupings = new CanvasGroupings(this.canvases, window.view);
}
}
/**
......@@ -74,29 +61,22 @@ export class WindowViewer extends Component {
* @return [Boolean]
*/
infoResponseIsInStore() {
const { currentCanvases } = this.props;
const responses = this.currentInfoResponses();
if (responses.length === this.currentCanvases().length) {
if (responses.length === currentCanvases.length) {
return true;
}
return false;
}
/**
* Uses CanvasGroupings to figure out how many and what canvases to present to
* a user based off of the view, number of canvases, and canvasIndex.
*/
currentCanvases() {
const { window } = this.props;
return this.canvasGroupings.getCanvases(window.canvasIndex);
}
/**
* currentInfoResponses - Selects infoResponses that are relevent to existing
* canvases to be displayed.
*/
currentInfoResponses() {
const { infoResponses } = this.props;
const currentCanvases = this.currentCanvases();
const { currentCanvases, infoResponses } = this.props;
return currentCanvases.map(canvas => (
infoResponses[new ManifestoCanvas(canvas).imageInformationUri]
)).filter(infoResponse => (infoResponse !== undefined
......@@ -108,10 +88,12 @@ export class WindowViewer extends Component {
* Return an image information response from the store for the correct image
*/
tileInfoFetchedFromStore() {
const { currentCanvases } = this.props;
const responses = this.currentInfoResponses()
.map(infoResponse => infoResponse.json);
// Only return actual tileSources when all current canvases have completed.
if (responses.length === this.currentCanvases().length) {
if (responses.length === currentCanvases.length) {
return responses;
}
return [];
......@@ -126,10 +108,9 @@ export class WindowViewer extends Component {
<>
<OSDViewer
tileSources={this.tileInfoFetchedFromStore()}
currentCanvases={this.currentCanvases()}
windowId={window.id}
>
<WindowCanvasNavigationControls windowId={window.id} canvases={this.canvases} />
<WindowCanvasNavigationControls windowId={window.id} />
</OSDViewer>
</>
);
......@@ -137,7 +118,7 @@ export class WindowViewer extends Component {
}
WindowViewer.propTypes = {
canvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
currentCanvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
fetchAnnotation: PropTypes.func.isRequired,
fetchInfoResponse: PropTypes.func.isRequired,
infoResponses: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
......
......@@ -6,10 +6,12 @@ import { fade } from '@material-ui/core/styles/colorManipulator';
import { withPlugins } from '../extend';
import { OpenSeadragonViewer } from '../components/OpenSeadragonViewer';
import * as actions from '../state/actions';
import CanvasWorld from '../lib/CanvasWorld';
import {
getAllOrSelectedAnnotations,
getCanvasLabel,
getSelectedAnnotationIds,
getSelectedCanvases,
} from '../state/selectors';
/**
......@@ -17,23 +19,16 @@ import {
* @memberof Window
* @private
*/
const mapStateToProps = ({
viewers, windows, manifests, annotations,
}, { windowId, currentCanvases }) => ({
const mapStateToProps = (state, { windowId }) => ({
annotations: getAllOrSelectedAnnotations(
{ annotations, windows },
state,
windowId,
currentCanvases.map(c => c.id),
getSelectedAnnotationIds({ windows }, windowId, currentCanvases.map(c => c.id)),
getSelectedCanvases(state, { windowId }),
getSelectedAnnotationIds(state, windowId, getSelectedCanvases(state, { windowId })),
),
label: getCanvasLabel({
manifests,
windows,
}, {
canvasIndex: 'selected',
windowId,
}),
viewer: viewers[windowId],
canvasWorld: new CanvasWorld(getSelectedCanvases(state, { windowId })),
label: getCanvasLabel(state, { canvasIndex: 'selected', windowId }),
viewer: state.viewers[windowId],
});
/**
......
......@@ -3,8 +3,14 @@ import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import { withPlugins } from '../extend';
import * as actions from '../state/actions';
import { getManifestCanvases } from '../state/selectors';
import { ViewerNavigation } from '../components/ViewerNavigation';
/** */
const mapStateToProps = (state, { window }) => ({
canvases: getManifestCanvases(state, { windowId: window.id }),
});
/**
* mapDispatchToProps - used to hook up connect to action creators
* @memberof ManifestForm
......@@ -16,7 +22,7 @@ const mapDispatchToProps = {
const enhance = compose(
withTranslation(),
connect(null, mapDispatchToProps),
connect(mapStateToProps, mapDispatchToProps),
withPlugins('ViewerNavigation'),
// further HOC go here
);
......
......@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
import { withPlugins } from '../extend';
import * as actions from '../state/actions';
import { WindowViewer } from '../components/WindowViewer';
import { getManifestCanvases } from '../state/selectors';
import { getSelectedCanvases } from '../state/selectors';
/**
* mapStateToProps - to hook up connect
......@@ -12,7 +12,7 @@ import { getManifestCanvases } from '../state/selectors';
*/
const mapStateToProps = (state, { window }) => (
{
canvases: getManifestCanvases(state, { windowId: window.id }),
currentCanvases: getSelectedCanvases(state, { windowId: window.id }),
infoResponses: state.infoResponses,
}
);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment