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),
+);