diff --git a/__tests__/src/actions/canvas.test.js b/__tests__/src/actions/canvas.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..f1e0b0d251f2f26fb432b0ed7fc85b5753b98f89
--- /dev/null
+++ b/__tests__/src/actions/canvas.test.js
@@ -0,0 +1,25 @@
+import * as actions from '../../../src/actions/index';
+import ActionTypes from '../../../src/action-types';
+
+describe('canvas actions', () => {
+  describe('nextCanvas', () => {
+    it('moves to the next canvas', () => {
+      const id = 'abc123';
+      const expectedAction = {
+        type: ActionTypes.NEXT_CANVAS,
+        windowId: id,
+      };
+      expect(actions.nextCanvas(id)).toEqual(expectedAction);
+    });
+  });
+  describe('previousCanvas', () => {
+    it('moves to the previous canvas', () => {
+      const id = 'abc123';
+      const expectedAction = {
+        type: ActionTypes.PREVIOUS_CANVAS,
+        windowId: id,
+      };
+      expect(actions.previousCanvas(id)).toEqual(expectedAction);
+    });
+  });
+});
diff --git a/__tests__/src/actions/config.test.js b/__tests__/src/actions/config.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..19e920bdde877a0c63d760e3cedabc2ec5c18b9b
--- /dev/null
+++ b/__tests__/src/actions/config.test.js
@@ -0,0 +1,25 @@
+import * as actions from '../../../src/actions/index';
+import ActionTypes from '../../../src/action-types';
+
+describe('config actions', () => {
+  describe('setConfig', () => {
+    it('sets the config', () => {
+      const config = { foo: 'bar' };
+      const expectedAction = {
+        type: ActionTypes.SET_CONFIG,
+        config,
+      };
+      expect(actions.setConfig(config)).toEqual(expectedAction);
+    });
+  });
+  describe('updateConfig', () => {
+    it('updates the config', () => {
+      const config = { foo: 'bar' };
+      const expectedAction = {
+        type: ActionTypes.UPDATE_CONFIG,
+        config,
+      };
+      expect(actions.updateConfig(config)).toEqual(expectedAction);
+    });
+  });
+});
diff --git a/__tests__/src/actions/index.test.js b/__tests__/src/actions/index.test.js
deleted file mode 100644
index 1dd4c73127380b22a29b4a904e46290115d45955..0000000000000000000000000000000000000000
--- a/__tests__/src/actions/index.test.js
+++ /dev/null
@@ -1,209 +0,0 @@
-import configureMockStore from 'redux-mock-store';
-import thunk from 'redux-thunk';
-
-import * as actions from '../../../src/actions/index';
-import ActionTypes from '../../../src/action-types';
-
-const middlewares = [thunk];
-const mockStore = configureMockStore(middlewares);
-
-describe('actions', () => {
-  describe('addWindow', () => {
-    it('should create a new window with merged defaults', () => {
-      const options = {
-        id: 'helloworld',
-        canvasIndex: 1,
-      };
-
-      const expectedAction = {
-        type: ActionTypes.ADD_WINDOW,
-        payload: {
-          id: 'helloworld',
-          canvasIndex: 1,
-          collectionIndex: 0,
-          manifestId: null,
-          rangeId: null,
-          xywh: [0, 0, 400, 400],
-          rotation: null,
-        },
-      };
-      expect(actions.addWindow(options)).toEqual(expectedAction);
-    });
-  });
-  describe('removeWindow', () => {
-    it('removes the window and returns windowId', () => {
-      const id = 'abc123';
-      const expectedAction = {
-        type: ActionTypes.REMOVE_WINDOW,
-        windowId: id,
-      };
-      expect(actions.removeWindow(id)).toEqual(expectedAction);
-    });
-  });
-  describe('nextCanvas', () => {
-    it('moves to the next canvas', () => {
-      const id = 'abc123';
-      const expectedAction = {
-        type: ActionTypes.NEXT_CANVAS,
-        windowId: id,
-      };
-      expect(actions.nextCanvas(id)).toEqual(expectedAction);
-    });
-  });
-  describe('previousCanvas', () => {
-    it('moves to the previous canvas', () => {
-      const id = 'abc123';
-      const expectedAction = {
-        type: ActionTypes.PREVIOUS_CANVAS,
-        windowId: id,
-      };
-      expect(actions.previousCanvas(id)).toEqual(expectedAction);
-    });
-  });
-  describe('requestManifest', () => {
-    it('requests a manifest given a url', () => {
-      const id = 'abc123';
-      const expectedAction = {
-        type: ActionTypes.REQUEST_MANIFEST,
-        manifestId: id,
-      };
-      expect(actions.requestManifest(id)).toEqual(expectedAction);
-    });
-  });
-  describe('receiveManifest', () => {
-    it('moves to the previous canvas', () => {
-      const id = 'abc123';
-      const json = {
-        id,
-        content: 'lots of metadata, canvases, and other IIIFy things',
-      };
-      const expectedAction = {
-        type: ActionTypes.RECEIVE_MANIFEST,
-        manifestId: id,
-        manifestJson: json,
-      };
-      expect(actions.receiveManifest(id, json)).toEqual(expectedAction);
-    });
-  });
-  describe('fetchManifest', () => {
-    let store = null;
-    beforeEach(() => {
-      store = mockStore({});
-    });
-    describe('success response', () => {
-      beforeEach(() => {
-        fetch.mockResponseOnce(JSON.stringify({ data: '12345' })); // eslint-disable-line no-undef
-      });
-      it('dispatches the REQUEST_MANIFEST action', () => {
-        store.dispatch(actions.fetchManifest('https://purl.stanford.edu/sn904cj3429/iiif/manifest'));
-        expect(store.getActions()).toEqual([
-          { manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', type: 'REQUEST_MANIFEST' },
-        ]);
-      });
-      it('dispatches the REQUEST_MANIFEST and then RECEIVE_MANIFEST', () => {
-        store.dispatch(actions.fetchManifest('https://purl.stanford.edu/sn904cj3429/iiif/manifest'))
-          .then(() => {
-            const expectedActions = store.getActions();
-            expect(expectedActions).toEqual([
-              { manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', type: 'REQUEST_MANIFEST' },
-              { manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', manifestJson: { data: '12345' }, type: 'RECEIVE_MANIFEST' },
-            ]);
-          });
-      });
-    });
-    describe('error response', () => {
-      it('dispatches the REQUEST_MANIFEST and then RECEIVE_MANIFEST', () => {
-        store.dispatch(actions.fetchManifest('https://purl.stanford.edu/sn904cj3429/iiif/manifest'))
-          .then(() => {
-            const expectedActions = store.getActions();
-            expect(expectedActions).toEqual([
-              { manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', type: 'REQUEST_MANIFEST' },
-              { manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', error: new Error('invalid json response body at undefined reason: Unexpected end of JSON input'), type: 'RECEIVE_MANIFEST_FAILURE' },
-            ]);
-          });
-      });
-    });
-  });
-  describe('removeManifest', () => {
-    it('removes an existing manifest', () => {
-      const expectedAction = {
-        type: ActionTypes.REMOVE_MANIFEST,
-        manifestId: 'foo',
-      };
-      expect(actions.removeManifest('foo')).toEqual(expectedAction);
-    });
-  });
-  describe('requestInfoResponse', () => {
-    it('requests an infoResponse from given a url', () => {
-      const id = 'abc123';
-      const expectedAction = {
-        type: ActionTypes.REQUEST_INFO_RESPONSE,
-        infoId: id,
-      };
-      expect(actions.requestInfoResponse(id)).toEqual(expectedAction);
-    });
-  });
-  describe('receiveInfoResponse', () => {
-    it('recieves an infoResponse', () => {
-      const id = 'abc123';
-      const json = {
-        id,
-        content: 'image information request',
-      };
-      const expectedAction = {
-        type: ActionTypes.RECEIVE_INFO_RESPONSE,
-        infoId: id,
-        infoJson: json,
-      };
-      expect(actions.receiveInfoResponse(id, json)).toEqual(expectedAction);
-    });
-  });
-  describe('fetchInfoResponse', () => {
-    let store = null;
-    beforeEach(() => {
-      store = mockStore({});
-    });
-    describe('success response', () => {
-      beforeEach(() => {
-        fetch.mockResponseOnce(JSON.stringify({ data: '12345' })); // eslint-disable-line no-undef
-      });
-      it('dispatches the REQUEST_MANIFEST action', () => {
-        store.dispatch(actions.fetchInfoResponse('https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json'));
-        expect(store.getActions()).toEqual([
-          { infoId: 'https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json', type: 'REQUEST_INFO_RESPONSE' },
-        ]);
-      });
-      it('dispatches the REQUEST_MANIFEST and then RECEIVE_MANIFEST', () => {
-        store.dispatch(actions.fetchInfoResponse('https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json'))
-          .then(() => {
-            const expectedActions = store.getActions();
-            expect(expectedActions).toEqual([
-              { infoId: 'https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json', type: 'REQUEST_INFO_RESPONSE' },
-              { infoId: 'https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json', infoJson: { data: '12345' }, type: 'RECEIVE_INFO_RESPONSE' },
-            ]);
-          });
-      });
-    });
-    describe('error response', () => {
-      it('dispatches the REQUEST_INFO_RESPONSE and then RECEIVE_INFO_RESPONSE', () => {
-        store.dispatch(actions.fetchInfoResponse('https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json'))
-          .then(() => {
-            const expectedActions = store.getActions();
-            expect(expectedActions).toEqual([
-              { infoId: 'https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json', type: 'REQUEST_INFO_RESPONSE' },
-              { infoId: 'https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json', error: new Error('invalid json response body at undefined reason: Unexpected end of JSON input'), type: 'RECEIVE_INFO_RESPONSE_FAILURE' },
-            ]);
-          });
-      });
-    });
-  });
-  describe('removeInfoResponse', () => {
-    it('removes an existing infoResponse', () => {
-      const expectedAction = {
-        type: ActionTypes.REMOVE_INFO_RESPONSE,
-        infoId: 'foo',
-      };
-      expect(actions.removeInfoResponse('foo')).toEqual(expectedAction);
-    });
-  });
-});
diff --git a/__tests__/src/actions/infoResponse.test.js b/__tests__/src/actions/infoResponse.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..56845d4142439e3e0f1057c8a7af8856ed29a4a2
--- /dev/null
+++ b/__tests__/src/actions/infoResponse.test.js
@@ -0,0 +1,84 @@
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+
+import * as actions from '../../../src/actions/index';
+import ActionTypes from '../../../src/action-types';
+
+const middlewares = [thunk];
+const mockStore = configureMockStore(middlewares);
+
+describe('infoResponse actions', () => {
+  describe('requestInfoResponse', () => {
+    it('requests an infoResponse from given a url', () => {
+      const id = 'abc123';
+      const expectedAction = {
+        type: ActionTypes.REQUEST_INFO_RESPONSE,
+        infoId: id,
+      };
+      expect(actions.requestInfoResponse(id)).toEqual(expectedAction);
+    });
+  });
+  describe('receiveInfoResponse', () => {
+    it('recieves an infoResponse', () => {
+      const id = 'abc123';
+      const json = {
+        id,
+        content: 'image information request',
+      };
+      const expectedAction = {
+        type: ActionTypes.RECEIVE_INFO_RESPONSE,
+        infoId: id,
+        infoJson: json,
+      };
+      expect(actions.receiveInfoResponse(id, json)).toEqual(expectedAction);
+    });
+  });
+  describe('fetchInfoResponse', () => {
+    let store = null;
+    beforeEach(() => {
+      store = mockStore({});
+    });
+    describe('success response', () => {
+      beforeEach(() => {
+        fetch.mockResponseOnce(JSON.stringify({ data: '12345' })); // eslint-disable-line no-undef
+      });
+      it('dispatches the REQUEST_MANIFEST action', () => {
+        store.dispatch(actions.fetchInfoResponse('https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json'));
+        expect(store.getActions()).toEqual([
+          { infoId: 'https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json', type: 'REQUEST_INFO_RESPONSE' },
+        ]);
+      });
+      it('dispatches the REQUEST_MANIFEST and then RECEIVE_MANIFEST', () => {
+        store.dispatch(actions.fetchInfoResponse('https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json'))
+          .then(() => {
+            const expectedActions = store.getActions();
+            expect(expectedActions).toEqual([
+              { infoId: 'https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json', type: 'REQUEST_INFO_RESPONSE' },
+              { infoId: 'https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json', infoJson: { data: '12345' }, type: 'RECEIVE_INFO_RESPONSE' },
+            ]);
+          });
+      });
+    });
+    describe('error response', () => {
+      it('dispatches the REQUEST_INFO_RESPONSE and then RECEIVE_INFO_RESPONSE', () => {
+        store.dispatch(actions.fetchInfoResponse('https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json'))
+          .then(() => {
+            const expectedActions = store.getActions();
+            expect(expectedActions).toEqual([
+              { infoId: 'https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json', type: 'REQUEST_INFO_RESPONSE' },
+              { infoId: 'https://stacks.stanford.edu/image/iiif/sn904cj3429%2F12027000/info.json', error: new Error('invalid json response body at undefined reason: Unexpected end of JSON input'), type: 'RECEIVE_INFO_RESPONSE_FAILURE' },
+            ]);
+          });
+      });
+    });
+  });
+  describe('removeInfoResponse', () => {
+    it('removes an existing infoResponse', () => {
+      const expectedAction = {
+        type: ActionTypes.REMOVE_INFO_RESPONSE,
+        infoId: 'foo',
+      };
+      expect(actions.removeInfoResponse('foo')).toEqual(expectedAction);
+    });
+  });
+});
diff --git a/__tests__/src/actions/manifest.test.js b/__tests__/src/actions/manifest.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..73c76998a323847b580ffeefafdcc416b8e36d4a
--- /dev/null
+++ b/__tests__/src/actions/manifest.test.js
@@ -0,0 +1,84 @@
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+
+import * as actions from '../../../src/actions/index';
+import ActionTypes from '../../../src/action-types';
+
+const middlewares = [thunk];
+const mockStore = configureMockStore(middlewares);
+
+describe('manifest actions', () => {
+  describe('requestManifest', () => {
+    it('requests a manifest given a url', () => {
+      const id = 'abc123';
+      const expectedAction = {
+        type: ActionTypes.REQUEST_MANIFEST,
+        manifestId: id,
+      };
+      expect(actions.requestManifest(id)).toEqual(expectedAction);
+    });
+  });
+  describe('receiveManifest', () => {
+    it('moves to the previous canvas', () => {
+      const id = 'abc123';
+      const json = {
+        id,
+        content: 'lots of metadata, canvases, and other IIIFy things',
+      };
+      const expectedAction = {
+        type: ActionTypes.RECEIVE_MANIFEST,
+        manifestId: id,
+        manifestJson: json,
+      };
+      expect(actions.receiveManifest(id, json)).toEqual(expectedAction);
+    });
+  });
+  describe('fetchManifest', () => {
+    let store = null;
+    beforeEach(() => {
+      store = mockStore({});
+    });
+    describe('success response', () => {
+      beforeEach(() => {
+        fetch.mockResponseOnce(JSON.stringify({ data: '12345' })); // eslint-disable-line no-undef
+      });
+      it('dispatches the REQUEST_MANIFEST action', () => {
+        store.dispatch(actions.fetchManifest('https://purl.stanford.edu/sn904cj3429/iiif/manifest'));
+        expect(store.getActions()).toEqual([
+          { manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', type: 'REQUEST_MANIFEST' },
+        ]);
+      });
+      it('dispatches the REQUEST_MANIFEST and then RECEIVE_MANIFEST', () => {
+        store.dispatch(actions.fetchManifest('https://purl.stanford.edu/sn904cj3429/iiif/manifest'))
+          .then(() => {
+            const expectedActions = store.getActions();
+            expect(expectedActions).toEqual([
+              { manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', type: 'REQUEST_MANIFEST' },
+              { manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', manifestJson: { data: '12345' }, type: 'RECEIVE_MANIFEST' },
+            ]);
+          });
+      });
+    });
+    describe('error response', () => {
+      it('dispatches the REQUEST_MANIFEST and then RECEIVE_MANIFEST', () => {
+        store.dispatch(actions.fetchManifest('https://purl.stanford.edu/sn904cj3429/iiif/manifest'))
+          .then(() => {
+            const expectedActions = store.getActions();
+            expect(expectedActions).toEqual([
+              { manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', type: 'REQUEST_MANIFEST' },
+              { manifestId: 'https://purl.stanford.edu/sn904cj3429/iiif/manifest', error: new Error('invalid json response body at undefined reason: Unexpected end of JSON input'), type: 'RECEIVE_MANIFEST_FAILURE' },
+            ]);
+          });
+      });
+    });
+  });
+  describe('removeManifest', () => {
+    it('removes an existing manifest', () => {
+      const expectedAction = {
+        type: ActionTypes.REMOVE_MANIFEST,
+        manifestId: 'foo',
+      };
+      expect(actions.removeManifest('foo')).toEqual(expectedAction);
+    });
+  });
+});
diff --git a/__tests__/src/actions/window.test.js b/__tests__/src/actions/window.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..e856f54f63a436e38366df46840fb49d69998669
--- /dev/null
+++ b/__tests__/src/actions/window.test.js
@@ -0,0 +1,37 @@
+import * as actions from '../../../src/actions/index';
+import ActionTypes from '../../../src/action-types';
+
+describe('window actions', () => {
+  describe('addWindow', () => {
+    it('should create a new window with merged defaults', () => {
+      const options = {
+        id: 'helloworld',
+        canvasIndex: 1,
+      };
+
+      const expectedAction = {
+        type: ActionTypes.ADD_WINDOW,
+        payload: {
+          id: 'helloworld',
+          canvasIndex: 1,
+          collectionIndex: 0,
+          manifestId: null,
+          rangeId: null,
+          xywh: [0, 0, 400, 400],
+          rotation: null,
+        },
+      };
+      expect(actions.addWindow(options)).toEqual(expectedAction);
+    });
+  });
+  describe('removeWindow', () => {
+    it('removes the window and returns windowId', () => {
+      const id = 'abc123';
+      const expectedAction = {
+        type: ActionTypes.REMOVE_WINDOW,
+        windowId: id,
+      };
+      expect(actions.removeWindow(id)).toEqual(expectedAction);
+    });
+  });
+});
diff --git a/src/actions/canvas.js b/src/actions/canvas.js
new file mode 100644
index 0000000000000000000000000000000000000000..a08efacfdc5d315273c6e4d8b54958247186c0ae
--- /dev/null
+++ b/src/actions/canvas.js
@@ -0,0 +1,21 @@
+import ActionTypes from '../action-types';
+
+/**
+ * nextCanvas - action creator
+ *
+ * @param  {String} windowId
+ * @memberof ActionCreators
+ */
+export function nextCanvas(windowId) {
+  return { type: ActionTypes.NEXT_CANVAS, windowId };
+}
+
+/**
+ * previousCanvas - action creator
+ *
+ * @param  {String} windowId
+ * @memberof ActionCreators
+ */
+export function previousCanvas(windowId) {
+  return { type: ActionTypes.PREVIOUS_CANVAS, windowId };
+}
diff --git a/src/actions/config.js b/src/actions/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..decd119f077000aeecc74423d02941dcec9db123
--- /dev/null
+++ b/src/actions/config.js
@@ -0,0 +1,21 @@
+import ActionTypes from '../action-types';
+
+/**
+ * setConfig - action creator
+ *
+ * @param  {Object} config
+* @memberof ActionCreators
+ */
+export function setConfig(config) {
+  return { type: ActionTypes.SET_CONFIG, config };
+}
+
+/**
+ * updateConfig - action creator
+ *
+ * @param  {Object} config
+* @memberof ActionCreators
+ */
+export function updateConfig(config) {
+  return { type: ActionTypes.UPDATE_CONFIG, config };
+}
diff --git a/src/actions/index.js b/src/actions/index.js
index ec56ae64a9757709b08108efe0f7ad38a45055b9..fb7d572b6c6d56cbceed27de31498e5b6d1d3288 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -1,227 +1,9 @@
-import fetch from 'node-fetch';
-import ActionTypes from '../action-types';
-
 /**
  * Action Creators for Mirador
  * @namespace ActionCreators
  */
-
-
-/**
- * setConfig - action creator
- *
- * @param  {Object} config
-* @memberof ActionCreators
- */
-export function setConfig(config) {
-  return { type: ActionTypes.SET_CONFIG, config };
-}
-
-/**
- * updateConfig - action creator
- *
- * @param  {Object} config
-* @memberof ActionCreators
- */
-export function updateConfig(config) {
-  return { type: ActionTypes.UPDATE_CONFIG, config };
-}
-
-/**
- * focusWindow - action creator
- *
- * @param  {String} windowId
- * @memberof ActionCreators
- */
-export function focusWindow(windowId) {
-  return { type: ActionTypes.FOCUS_WINDOW, windowId };
-}
-
-/**
- * addWindow - action creator
- *
- * @param  {Object} options
- * @memberof ActionCreators
- */
-export function addWindow(options) {
-  const defaultOptions = {
-    // TODO: Windows should be a hash with id's as keys for easy lookups
-    // https://redux.js.org/faq/organizing-state#how-do-i-organize-nested-or-duplicate-data-in-my-state
-    id: `window-${new Date().valueOf()}`,
-    canvasIndex: 0,
-    collectionIndex: 0,
-    manifestId: null,
-    rangeId: null,
-    xywh: [0, 0, 400, 400],
-    rotation: null,
-  };
-  return { type: ActionTypes.ADD_WINDOW, payload: Object.assign({}, defaultOptions, options) };
-}
-
-/**
- * removeWindow - action creator
- *
- * @param  {String} windowId
- * @memberof ActionCreators
- */
-export function removeWindow(windowId) {
-  return { type: ActionTypes.REMOVE_WINDOW, windowId };
-}
-
-/**
- * nextCanvas - action creator
- *
- * @param  {String} windowId
- * @memberof ActionCreators
- */
-export function nextCanvas(windowId) {
-  return { type: ActionTypes.NEXT_CANVAS, windowId };
-}
-
-/**
- * previousCanvas - action creator
- *
- * @param  {String} windowId
- * @memberof ActionCreators
- */
-export function previousCanvas(windowId) {
-  return { type: ActionTypes.PREVIOUS_CANVAS, windowId };
-}
-
-/**
- * requestManifest - action creator
- *
- * @param  {String} manifestId
- * @memberof ActionCreators
- */
-export function requestManifest(manifestId) {
-  return {
-    type: ActionTypes.REQUEST_MANIFEST,
-    manifestId,
-  };
-}
-
-/**
- * receiveManifest - action creator
- *
- * @param  {String} windowId
- * @param  {Object} manifestJson
- * @memberof ActionCreators
- */
-export function receiveManifest(manifestId, manifestJson) {
-  return {
-    type: ActionTypes.RECEIVE_MANIFEST,
-    manifestId,
-    manifestJson,
-  };
-}
-
-/**
- * receiveManifestFailure - action creator
- *
- * @param  {String} windowId
- * @param  {String} error
- * @memberof ActionCreators
- */
-export function receiveManifestFailure(manifestId, error) {
-  return {
-    type: ActionTypes.RECEIVE_MANIFEST_FAILURE,
-    manifestId,
-    error,
-  };
-}
-
-/**
- * fetchManifest - action creator
- *
- * @param  {String} manifestId
- * @memberof ActionCreators
- */
-export function fetchManifest(manifestId) {
-  return ((dispatch) => {
-    dispatch(requestManifest(manifestId));
-    return fetch(manifestId)
-      .then(response => response.json())
-      .then(json => dispatch(receiveManifest(manifestId, json)))
-      .catch(error => dispatch(receiveManifestFailure(manifestId, error)));
-  });
-}
-
-/**
- * removeManifest - action creator
- *
- * @param  {String} manifestId
- * @memberof ActionCreators
- */
-export function removeManifest(manifestId) {
-  return { type: ActionTypes.REMOVE_MANIFEST, manifestId };
-}
-
-/**
- * requestInfoResponse - action creator
- *
- * @param  {String} infoId
- * @memberof ActionCreators
- */
-export function requestInfoResponse(infoId) {
-  return {
-    type: ActionTypes.REQUEST_INFO_RESPONSE,
-    infoId,
-  };
-}
-
-/**
- * receiveInfoResponse - action creator
- *
- * @param  {String} infoId
- * @param  {Object} manifestJson
- * @memberof ActionCreators
- */
-export function receiveInfoResponse(infoId, infoJson) {
-  return {
-    type: ActionTypes.RECEIVE_INFO_RESPONSE,
-    infoId,
-    infoJson,
-  };
-}
-
-/**
- * receiveInfoResponseFailure - action creator
- *
- * @param  {String} infoId
- * @param  {String} error
- * @memberof ActionCreators
- */
-export function receiveInfoResponseFailure(infoId, error) {
-  return {
-    type: ActionTypes.RECEIVE_INFO_RESPONSE_FAILURE,
-    infoId,
-    error,
-  };
-}
-
-/**
- * fetchInfoResponse - action creator
- *
- * @param  {String} infoId
- * @memberof ActionCreators
- */
-export function fetchInfoResponse(infoId) {
-  return ((dispatch) => {
-    dispatch(requestInfoResponse(infoId));
-    return fetch(infoId)
-      .then(response => response.json())
-      .then(json => dispatch(receiveInfoResponse(infoId, json)))
-      .catch(error => dispatch(receiveInfoResponseFailure(infoId, error)));
-  });
-}
-
-/**
- * removeInfoResponse - action creator
- *
- * @param  {String} infoId
- * @memberof ActionCreators
- */
-export function removeInfoResponse(infoId) {
-  return { type: ActionTypes.REMOVE_INFO_RESPONSE, infoId };
-}
+export * from './config';
+export * from './window';
+export * from './manifest';
+export * from './infoResponse';
+export * from './canvas';
diff --git a/src/actions/infoResponse.js b/src/actions/infoResponse.js
new file mode 100644
index 0000000000000000000000000000000000000000..a38c9dacee6c17b03cac9910450eb90dda5299e9
--- /dev/null
+++ b/src/actions/infoResponse.js
@@ -0,0 +1,71 @@
+import fetch from 'node-fetch';
+import ActionTypes from '../action-types';
+
+/**
+ * requestInfoResponse - action creator
+ *
+ * @param  {String} infoId
+ * @memberof ActionCreators
+ */
+export function requestInfoResponse(infoId) {
+  return {
+    type: ActionTypes.REQUEST_INFO_RESPONSE,
+    infoId,
+  };
+}
+
+/**
+ * receiveInfoResponse - action creator
+ *
+ * @param  {String} infoId
+ * @param  {Object} manifestJson
+ * @memberof ActionCreators
+ */
+export function receiveInfoResponse(infoId, infoJson) {
+  return {
+    type: ActionTypes.RECEIVE_INFO_RESPONSE,
+    infoId,
+    infoJson,
+  };
+}
+
+/**
+ * receiveInfoResponseFailure - action creator
+ *
+ * @param  {String} infoId
+ * @param  {String} error
+ * @memberof ActionCreators
+ */
+export function receiveInfoResponseFailure(infoId, error) {
+  return {
+    type: ActionTypes.RECEIVE_INFO_RESPONSE_FAILURE,
+    infoId,
+    error,
+  };
+}
+
+/**
+ * fetchInfoResponse - action creator
+ *
+ * @param  {String} infoId
+ * @memberof ActionCreators
+ */
+export function fetchInfoResponse(infoId) {
+  return ((dispatch) => {
+    dispatch(requestInfoResponse(infoId));
+    return fetch(infoId)
+      .then(response => response.json())
+      .then(json => dispatch(receiveInfoResponse(infoId, json)))
+      .catch(error => dispatch(receiveInfoResponseFailure(infoId, error)));
+  });
+}
+
+/**
+ * removeInfoResponse - action creator
+ *
+ * @param  {String} infoId
+ * @memberof ActionCreators
+ */
+export function removeInfoResponse(infoId) {
+  return { type: ActionTypes.REMOVE_INFO_RESPONSE, infoId };
+}
diff --git a/src/actions/manifest.js b/src/actions/manifest.js
new file mode 100644
index 0000000000000000000000000000000000000000..1f5a58006deea0119986c7e321ce808220985108
--- /dev/null
+++ b/src/actions/manifest.js
@@ -0,0 +1,71 @@
+import fetch from 'node-fetch';
+import ActionTypes from '../action-types';
+
+/**
+ * requestManifest - action creator
+ *
+ * @param  {String} manifestId
+ * @memberof ActionCreators
+ */
+export function requestManifest(manifestId) {
+  return {
+    type: ActionTypes.REQUEST_MANIFEST,
+    manifestId,
+  };
+}
+
+/**
+ * receiveManifest - action creator
+ *
+ * @param  {String} windowId
+ * @param  {Object} manifestJson
+ * @memberof ActionCreators
+ */
+export function receiveManifest(manifestId, manifestJson) {
+  return {
+    type: ActionTypes.RECEIVE_MANIFEST,
+    manifestId,
+    manifestJson,
+  };
+}
+
+/**
+ * receiveManifestFailure - action creator
+ *
+ * @param  {String} windowId
+ * @param  {String} error
+ * @memberof ActionCreators
+ */
+export function receiveManifestFailure(manifestId, error) {
+  return {
+    type: ActionTypes.RECEIVE_MANIFEST_FAILURE,
+    manifestId,
+    error,
+  };
+}
+
+/**
+ * fetchManifest - action creator
+ *
+ * @param  {String} manifestId
+ * @memberof ActionCreators
+ */
+export function fetchManifest(manifestId) {
+  return ((dispatch) => {
+    dispatch(requestManifest(manifestId));
+    return fetch(manifestId)
+      .then(response => response.json())
+      .then(json => dispatch(receiveManifest(manifestId, json)))
+      .catch(error => dispatch(receiveManifestFailure(manifestId, error)));
+  });
+}
+
+/**
+ * removeManifest - action creator
+ *
+ * @param  {String} manifestId
+ * @memberof ActionCreators
+ */
+export function removeManifest(manifestId) {
+  return { type: ActionTypes.REMOVE_MANIFEST, manifestId };
+}
diff --git a/src/actions/window.js b/src/actions/window.js
new file mode 100644
index 0000000000000000000000000000000000000000..f7c7fa7a9a57a88160e999094a0427d1afcaa444
--- /dev/null
+++ b/src/actions/window.js
@@ -0,0 +1,42 @@
+import ActionTypes from '../action-types';
+
+/**
+ * focusWindow - action creator
+ *
+ * @param  {String} windowId
+ * @memberof ActionCreators
+ */
+export function focusWindow(windowId) {
+  return { type: ActionTypes.FOCUS_WINDOW, windowId };
+}
+
+/**
+ * addWindow - action creator
+ *
+ * @param  {Object} options
+ * @memberof ActionCreators
+ */
+export function addWindow(options) {
+  const defaultOptions = {
+    // TODO: Windows should be a hash with id's as keys for easy lookups
+    // https://redux.js.org/faq/organizing-state#how-do-i-organize-nested-or-duplicate-data-in-my-state
+    id: `window-${new Date().valueOf()}`,
+    canvasIndex: 0,
+    collectionIndex: 0,
+    manifestId: null,
+    rangeId: null,
+    xywh: [0, 0, 400, 400],
+    rotation: null,
+  };
+  return { type: ActionTypes.ADD_WINDOW, payload: Object.assign({}, defaultOptions, options) };
+}
+
+/**
+ * removeWindow - action creator
+ *
+ * @param  {String} windowId
+ * @memberof ActionCreators
+ */
+export function removeWindow(windowId) {
+  return { type: ActionTypes.REMOVE_WINDOW, windowId };
+}