diff --git a/__tests__/src/actions/window.test.js b/__tests__/src/actions/window.test.js index 42c2ad3d4e8ad38095a53c0cc0d5fce898b8df23..e7599b5d4b8f3b65aec0f6537d67f16ce662bba3 100644 --- a/__tests__/src/actions/window.test.js +++ b/__tests__/src/actions/window.test.js @@ -54,7 +54,6 @@ describe('window actions', () => { canvasIndex: 1, collectionIndex: 0, id: 'helloworld', - layoutOrder: 3, manifestId: null, maximized: false, rangeId: null, @@ -73,7 +72,7 @@ describe('window actions', () => { sideBarOpenByDefault: false, }, }, - windows: { a: {}, b: {} }, + workspace: { windowIds: ['a', 'b'] }, }; const mockDispatch = jest.fn(() => ({})); @@ -108,7 +107,7 @@ describe('window actions', () => { defaultSideBarPanel: 'info', }, }, - windows: {}, + workspace: {}, }; const mockDispatch = jest.fn(() => ({})); @@ -138,7 +137,7 @@ describe('window actions', () => { defaultSideBarPanel: null, }, }, - windows: {}, + workspace: {}, }; const mockDispatch = jest.fn(() => ({})); @@ -168,7 +167,7 @@ describe('window actions', () => { defaultSideBarPanel: null, }, }, - windows: {}, + workspace: {}, }; const mockDispatch = jest.fn(() => ({})); diff --git a/__tests__/src/components/WorkspaceMosaic.test.js b/__tests__/src/components/WorkspaceMosaic.test.js index efc150d3385528fe8abb6ee2f57c046f8ae8bf80..8224bd1ef6dc92ccebda8c06b8fcef04265e8e1b 100644 --- a/__tests__/src/components/WorkspaceMosaic.test.js +++ b/__tests__/src/components/WorkspaceMosaic.test.js @@ -9,7 +9,7 @@ function createWrapper(props) { return shallow( <WorkspaceMosaic classes={{}} - windows={{}} + windowIds={[]} workspaceId="foo" updateWorkspaceMosaicLayout={() => {}} {...props} @@ -18,10 +18,10 @@ function createWrapper(props) { } describe('WorkspaceMosaic', () => { - const windows = { 1: { id: 1 }, 2: { id: 2 } }; + const windowIds = ['1', '2']; let wrapper; beforeEach(() => { - wrapper = createWrapper({ windows }); + wrapper = createWrapper({ windowIds }); }); it('should render properly with an initialValue', () => { expect(wrapper.find(MosaicWithoutDragDropContext).length).toEqual(1); @@ -34,10 +34,10 @@ describe('WorkspaceMosaic', () => { const updateWorkspaceMosaicLayout = jest.fn(); wrapper = createWrapper({ updateWorkspaceMosaicLayout, - windows, + windowIds, }); - wrapper.setProps({ windows: { ...windows, 3: { id: 3 } } }); + wrapper.setProps({ windowIds: [...windowIds, '3'] }); expect(updateWorkspaceMosaicLayout).toHaveBeenCalled(); }); @@ -46,19 +46,19 @@ describe('WorkspaceMosaic', () => { wrapper = createWrapper({ layout: { first: 1, second: 2 }, updateWorkspaceMosaicLayout, - windows, + windowIds, }); wrapper.instance().windowPaths = { 2: ['second'] }; - wrapper.setProps({ windows: { 1: { id: 1 } } }); + wrapper.setProps({ windowIds: [1] }); expect(updateWorkspaceMosaicLayout).toHaveBeenLastCalledWith(1); }); it('when no windows remain', () => { const updateWorkspaceMosaicLayout = jest.fn(); wrapper = createWrapper({ updateWorkspaceMosaicLayout, - windows, + windowIds, }); - wrapper.setProps({ windows: {} }); + wrapper.setProps({ windowIds: [] }); expect(updateWorkspaceMosaicLayout).toHaveBeenLastCalledWith(null); }); it('when the new and old layouts are the same', () => { @@ -66,9 +66,9 @@ describe('WorkspaceMosaic', () => { wrapper = createWrapper({ layout: { first: 1, second: 2 }, updateWorkspaceMosaicLayout, - windows, + windowIds, }); - wrapper.setProps({ layout: { first: 1, second: 2 }, windows }); + wrapper.setProps({ layout: { first: 1, second: 2 }, windowIds }); expect(updateWorkspaceMosaicLayout).toHaveBeenCalledTimes(1); }); }); @@ -80,21 +80,21 @@ describe('WorkspaceMosaic', () => { }); describe('determineWorkspaceLayout', () => { it('when window ids do not match workspace layout', () => { - wrapper = createWrapper({ layout: {}, windows }); + wrapper = createWrapper({ layout: {}, windowIds }); expect(wrapper.instance().determineWorkspaceLayout()).toMatchObject({ direction: 'row', first: '1', second: '2', }); }); it('by default use workspace.layout', () => { - wrapper = createWrapper({ layout: {}, windows: { foo: 'bar' } }); + wrapper = createWrapper({ layout: {}, windowIds: ['foo'] }); expect(wrapper.instance().determineWorkspaceLayout()).toEqual('foo'); }); it('generates a new layout if windows do not match current layout', () => { - wrapper = createWrapper({ layout: { first: 'foo', second: 'bark' }, windows: { foo: 'bar' } }); + wrapper = createWrapper({ layout: { first: 'foo', second: 'bark' }, windowIds: ['foo'] }); expect(wrapper.instance().determineWorkspaceLayout()).toEqual('foo'); }); it('when window ids match workspace layout', () => { - wrapper = createWrapper({ layout: {}, windows: { foo: { id: 'foo' } } }); + wrapper = createWrapper({ layout: {}, windowIds: ['foo'] }); expect(wrapper.instance().determineWorkspaceLayout()).toBe('foo'); }); }); @@ -124,7 +124,7 @@ describe('WorkspaceMosaic', () => { const updateWorkspaceMosaicLayout = jest.fn(); wrapper = createWrapper({ updateWorkspaceMosaicLayout, - windows, + windowIds, }); wrapper.instance().mosaicChange(); diff --git a/__tests__/src/lib/MiradorViewer.test.js b/__tests__/src/lib/MiradorViewer.test.js index 17c55354396e5527cbb6647cd4d218c707944851..75e2d67b7a05dcc1879f75416445919c0f1dec1d 100644 --- a/__tests__/src/lib/MiradorViewer.test.js +++ b/__tests__/src/lib/MiradorViewer.test.js @@ -44,8 +44,6 @@ describe('MiradorViewer', () => { expect(Object.keys(windowIds).length).toBe(2); expect(windows[windowIds[0]].canvasId).toBe('https://iiif.harvardartmuseums.org/manifests/object/299843/canvas/canvas-47174892'); expect(windows[windowIds[1]].canvasId).toBe(undefined); - expect(windows[windowIds[0]].layoutOrder).toBe(0); - expect(windows[windowIds[1]].layoutOrder).toBe(1); expect(windows[windowIds[0]].thumbnailNavigationPosition).toBe('far-bottom'); expect(windows[windowIds[1]].thumbnailNavigationPosition).toBe(undefined); expect(windows[windowIds[0]].view).toBe(undefined); diff --git a/__tests__/src/sagas/app.test.js b/__tests__/src/sagas/app.test.js index 807e401cea53e8e28651c4b580b79613b93ea760..ce4467d0939cee103190f468ddb0deaba5f3e2d5 100644 --- a/__tests__/src/sagas/app.test.js +++ b/__tests__/src/sagas/app.test.js @@ -69,10 +69,10 @@ describe('app-level sagas', () => { return expectSaga(importConfig, action) .provide([ [call(addWindow, { - id: 'x', layoutOrder: 0, manifestId: 'a', thumbnailNavigationPosition: undefined, + id: 'x', manifestId: 'a', thumbnailNavigationPosition: undefined, }), { type: 'thunk1' }], [call(addWindow, { - id: 'y', layoutOrder: 1, manifestId: 'b', thumbnailNavigationPosition: undefined, + id: 'y', manifestId: 'b', thumbnailNavigationPosition: undefined, }), { type: 'thunk2' }], ]) .put({ type: 'thunk1' }) diff --git a/__tests__/src/selectors/getters.test.js b/__tests__/src/selectors/getters.test.js index 725af884f4c37aa18e00d78f7a45d629ed35b4f7..eabadc67310b7b09163697da979fc1e4c6ae7442 100644 --- a/__tests__/src/selectors/getters.test.js +++ b/__tests__/src/selectors/getters.test.js @@ -3,6 +3,7 @@ import { getViewer, getWindowManifests, getWindows, + getCatalog, } from '../../../src/state/selectors/getters'; describe('getManifest()', () => { @@ -92,3 +93,16 @@ describe('getViewer', () => { }); }); }); + +describe('getCatalog', () => { + const catalog = [{ a: 1 }]; + const state = { + catalog, + }; + + it('should return companion windows for a given window id', () => { + const received = getCatalog(state); + + expect(received).toEqual(catalog); + }); +}); diff --git a/__tests__/src/selectors/windows.test.js b/__tests__/src/selectors/windows.test.js index c8d5293708c3fe5127431db14e8718b0318171e6..0d86a9dc3f3289e79a4f2ef075749a88325e1311 100644 --- a/__tests__/src/selectors/windows.test.js +++ b/__tests__/src/selectors/windows.test.js @@ -24,6 +24,18 @@ describe('getWindowConfig', () => { expect(getWindowConfig(state, { windowId: 'a' })).toEqual({ a: '1', b: '3', c: '4' }); }); + it('gracefully handles missing windows', () => { + const state = { + config: { + window: { a: '1', b: '2' }, + }, + windows: { + a: {}, + }, + }; + + expect(getWindowConfig(state, { windowId: 'c' })).toEqual({ a: '1', b: '2' }); + }); }); describe('getMaximizedWindowsIds', () => { @@ -155,7 +167,6 @@ describe('getWindowDraggability', () => { describe('in elastic mode', () => { it('is always true', () => { const state = { - windows: {}, workspace: { type: 'elastic' }, }; const props = {}; @@ -167,8 +178,7 @@ describe('getWindowDraggability', () => { describe('in non-elastic mode', () => { it('is false if there is only one window', () => { const state = { - windows: { abc123: {} }, - workspace: { type: 'mosaic' }, + workspace: { type: 'mosaic', windowIds: ['abc123'] }, }; const props = { windowId: 'abc123' }; @@ -178,7 +188,7 @@ describe('getWindowDraggability', () => { it('is false when the window is maximized', () => { const state = { windows: { abc123: { maximized: true }, abc321: { maximized: false } }, - workspace: { type: 'mosaic' }, + workspace: { type: 'mosaic', windowIds: ['abc123', 'abc321'] }, }; const props = { windowId: 'abc123' }; @@ -188,7 +198,7 @@ describe('getWindowDraggability', () => { it('is true if there are many windows (as long as the window is not maximized)', () => { const state = { windows: { abc123: { maximized: false }, abc321: { maximized: false } }, - workspace: { type: 'mosaic' }, + workspace: { type: 'mosaic', windowIds: ['abc123', 'abc321'] }, }; const props = { windowId: 'abc123' }; diff --git a/__tests__/src/selectors/workspace.test.js b/__tests__/src/selectors/workspace.test.js index 8e2823c47811f6687793b26a8fede4462906110d..f4f78c5923d23d33099bbe83abc267d6965c0e46 100644 --- a/__tests__/src/selectors/workspace.test.js +++ b/__tests__/src/selectors/workspace.test.js @@ -1,6 +1,7 @@ import { getFullScreenEnabled, getWorkspaceType, + isFocused, } from '../../../src/state/selectors'; describe('getFullScreenEnabled', () => { @@ -16,3 +17,14 @@ describe('getWorkspaceType', () => { expect(getWorkspaceType(state)).toEqual('elastic'); }); }); + +describe('isFocused', () => { + it('is true if the window has focus', () => { + const state = { workspace: { focusedWindowId: 'a' } }; + expect(isFocused(state, { windowId: 'a' })).toEqual(true); + }); + it('is false if the window does not has focus', () => { + const state = { workspace: { focusedWindowId: 'a' } }; + expect(isFocused(state, { windowId: 'b' })).toEqual(false); + }); +}); diff --git a/src/components/SidebarIndexThumbnail.js b/src/components/SidebarIndexThumbnail.js index 44e095cf607afeee95407a8973f5d525bb5f9b7f..c2ab64128277c054228e349045a06de5299faf50 100644 --- a/src/components/SidebarIndexThumbnail.js +++ b/src/components/SidebarIndexThumbnail.js @@ -9,7 +9,7 @@ export class SidebarIndexThumbnail extends Component { /** */ render() { const { - classes, config, otherCanvas, canvas, + classes, otherCanvas, canvas, height, width, } = this.props; return ( @@ -18,8 +18,8 @@ export class SidebarIndexThumbnail extends Component { <IIIFThumbnail resource={otherCanvas} className={classNames(classes.clickable)} - maxHeight={config.canvasNavigation.height} - maxWidth={config.canvasNavigation.width} + maxHeight={height} + maxWidth={width} /> </div> <Typography @@ -36,6 +36,12 @@ export class SidebarIndexThumbnail extends Component { SidebarIndexThumbnail.propTypes = { canvas: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types classes: PropTypes.objectOf(PropTypes.string).isRequired, - config: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + height: PropTypes.number, otherCanvas: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + width: PropTypes.number, +}; + +SidebarIndexThumbnail.defaultProps = { + height: undefined, + width: undefined, }; diff --git a/src/components/WorkspaceMosaic.js b/src/components/WorkspaceMosaic.js index 93476bd8058722498640eb346aaed8f5ce7a0ad2..a7003be9c47c0f55feae4ec1601ea43cb2f6ea90 100644 --- a/src/components/WorkspaceMosaic.js +++ b/src/components/WorkspaceMosaic.js @@ -42,25 +42,24 @@ export class WorkspaceMosaic extends React.Component { /** */ componentDidUpdate(prevProps) { - const { windows, layout, updateWorkspaceMosaicLayout } = this.props; - const prevWindows = Object.keys(prevProps.windows); - const currentWindows = Object.keys(windows); + const { windowIds, layout, updateWorkspaceMosaicLayout } = this.props; + const prevWindows = prevProps.windowIds; // Handles when Windows are added (not via Add Resource UI) Could be a workspace import - if (!currentWindows.every(e => prevWindows.includes(e))) { + if (!windowIds.every(e => prevWindows.includes(e))) { const newLayout = this.determineWorkspaceLayout(); if (!isEqual(newLayout, layout)) updateWorkspaceMosaicLayout(newLayout); return; } - // console.log(prevWindows, currentWindows); + // Handles when Windows are removed from the state - if (!prevWindows.every(e => currentWindows.includes(e))) { + if (!prevWindows.every(e => windowIds.includes(e))) { // There are no more remaining Windows, just return an empty layout - if (currentWindows.length === 0) { + if (windowIds.length === 0) { updateWorkspaceMosaicLayout(null); return; } - const removedWindows = difference(prevWindows, currentWindows); + const removedWindows = difference(prevWindows, windowIds); const newLayout = new MosaicLayout(layout); newLayout.removeWindows(removedWindows, this.windowPaths); updateWorkspaceMosaicLayout(newLayout.layout); @@ -80,26 +79,24 @@ export class WorkspaceMosaic extends React.Component { * Used to determine whether or not a "new" layout should be autogenerated. */ determineWorkspaceLayout() { - const { windows, layout } = this.props; - const sortedWindows = toPairs(windows) - .sort((a, b) => a.layoutOrder - b.layoutOrder).map(val => val[0]); + const { windowIds, layout } = this.props; const leaveKeys = getLeaves(layout); // Windows were added - if (!sortedWindows.every(e => leaveKeys.includes(e))) { + if (!windowIds.every(e => leaveKeys.includes(e))) { // No current layout, so just generate a new one if (leaveKeys.length < 2) { - return createBalancedTreeFromLeaves(sortedWindows); + return createBalancedTreeFromLeaves(windowIds); } // Add new windows to layout - const addedWindows = difference(sortedWindows, leaveKeys); + const addedWindows = difference(windowIds, leaveKeys); const newLayout = new MosaicLayout(layout); newLayout.addWindows(addedWindows); return newLayout.layout; } // Windows were removed (perhaps in a different Workspace). We don't have a // way to reconfigure.. so we have to random generate - if (!leaveKeys.every(e => sortedWindows.includes(e))) { - return createBalancedTreeFromLeaves(sortedWindows); + if (!leaveKeys.every(e => windowIds.includes(e))) { + return createBalancedTreeFromLeaves(windowIds); } return layout; } @@ -117,21 +114,20 @@ export class WorkspaceMosaic extends React.Component { * Render a tile (Window) in the Mosaic. */ tileRenderer(id, path) { - const { windows, workspaceId } = this.props; - const window = windows[id]; - if (!window) return null; - this.bookkeepPath(window.id, path); + const { windowIds, workspaceId } = this.props; + if (!windowIds.includes(id)) return null; + this.bookkeepPath(id, path); return ( <MosaicWindow toolbarControls={this.toolbarControls} additionalControls={this.additionalControls} path={path} - windowId={window.id} + windowId={id} renderPreview={WorkspaceMosaic.renderPreview} > <Window - key={`${window.id}-${workspaceId}`} - windowId={window.id} + key={`${id}-${workspaceId}`} + windowId={id} /> </MosaicWindow> ); @@ -167,10 +163,11 @@ WorkspaceMosaic.propTypes = { [PropTypes.object, PropTypes.string], ), // eslint-disable-line react/forbid-prop-types updateWorkspaceMosaicLayout: PropTypes.func.isRequired, - windows: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types + windowIds: PropTypes.arrayOf(PropTypes.string), workspaceId: PropTypes.string.isRequired, }; WorkspaceMosaic.defaultProps = { layout: undefined, + windowIds: [], }; diff --git a/src/config/settings.js b/src/config/settings.js index 42d8cdbedcb73e5bc8284d7551f99ee63d0fb72e..010bcaa309845ae6db79391f50a509d6d3d1b5cc 100644 --- a/src/config/settings.js +++ b/src/config/settings.js @@ -1,6 +1,9 @@ import { v4 as uuid } from 'uuid'; export default { + state: { + // slice: 'mirador' // Configure the top-level slice of state for mirador selectors + }, canvasNavigation: { // Set the hight and width of canvas thumbnails in the CanvasNavigation companion window height: 50, width: 50, diff --git a/src/containers/AccessTokenSender.js b/src/containers/AccessTokenSender.js index 61a4edcce3c6ed63c55c819d98e87ce96f049598..eb8b51fea3aa7441fd6b2b04c43b33eec34a8b93 100644 --- a/src/containers/AccessTokenSender.js +++ b/src/containers/AccessTokenSender.js @@ -2,6 +2,7 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; +import { getAccessTokens } from '../state/selectors'; import { AccessTokenSender } from '../components/AccessTokenSender'; /** @@ -9,8 +10,8 @@ import { AccessTokenSender } from '../components/AccessTokenSender'; * @memberof App * @private */ -const mapStateToProps = ({ accessTokens }) => ({ - url: accessTokens && (Object.values(accessTokens).find(e => e.isFetching) || {}).id, +const mapStateToProps = (state) => ({ + url: (Object.values(getAccessTokens(state)).find(e => e.isFetching) || {}).id, }); /** diff --git a/src/containers/AnnotationSettings.js b/src/containers/AnnotationSettings.js index 530096e786fba5bcfff789cf4b1c7a4119fa2461..1c1dd8d0c616100d50018ddd2e6da39bd7d27006 100644 --- a/src/containers/AnnotationSettings.js +++ b/src/containers/AnnotationSettings.js @@ -16,7 +16,7 @@ const mapStateToProps = (state, { windowId }) => ({ displayAll: getWindow(state, { windowId }).highlightAllAnnotations, displayAllDisabled: getAnnotationResourcesByMotivation( state, - { motivations: state.config.annotations.filteredMotivations, windowId }, + { windowId }, ).length < 2, }); diff --git a/src/containers/AppProviders.js b/src/containers/AppProviders.js index 226621be110d055cfcd56670824136e0ec36cacf..9e2c86ca3fe7b31fbed5fe4c20998835478f12b0 100644 --- a/src/containers/AppProviders.js +++ b/src/containers/AppProviders.js @@ -2,7 +2,7 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; -import { getTheme } from '../state/selectors'; +import { getConfig, getTheme, getFullScreenEnabled } from '../state/selectors'; import { AppProviders } from '../components/AppProviders'; /** @@ -12,11 +12,11 @@ import { AppProviders } from '../components/AppProviders'; */ const mapStateToProps = state => ( { - classPrefix: state.config.classPrefix, - isFullscreenEnabled: state.workspace.isFullscreenEnabled, - language: state.config.language, + classPrefix: getConfig(state).classPrefix, + isFullscreenEnabled: getFullScreenEnabled(state), + language: getConfig(state).language, theme: getTheme(state), - translations: state.config.translations, + translations: getConfig(state).translations, } ); diff --git a/src/containers/AuthenticationSender.js b/src/containers/AuthenticationSender.js index c574259646d4a9461404d9058d14b274936bf1f3..97ab2847d775bc1e5467ac1f47f91ff88c4b12d7 100644 --- a/src/containers/AuthenticationSender.js +++ b/src/containers/AuthenticationSender.js @@ -2,6 +2,7 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; +import { getAuth, getConfig } from '../state/selectors'; import { AuthenticationSender } from '../components/AuthenticationSender'; /** @@ -9,9 +10,9 @@ import { AuthenticationSender } from '../components/AuthenticationSender'; * @memberof App * @private */ -const mapStateToProps = ({ auth, config }) => ({ - center: config.window.authNewWindowCenter, - url: auth && (Object.values(auth).find(e => e.isFetching && e.profile !== 'http://iiif.io/api/auth/1/external') || {}).id, +const mapStateToProps = (state) => ({ + center: getConfig(state).window.authNewWindowCenter, + url: (Object.values(getAuth(state)).find(e => e.isFetching && e.profile !== 'http://iiif.io/api/auth/1/external') || {}).id, }); /** diff --git a/src/containers/CanvasAnnotations.js b/src/containers/CanvasAnnotations.js index 31245670d7138c6ba268de22d62e2d9e7c2c4c87..f44fc9f03319e00e01ece4a5a67f25f6a9c9a4ac 100644 --- a/src/containers/CanvasAnnotations.js +++ b/src/containers/CanvasAnnotations.js @@ -8,6 +8,7 @@ import { getAnnotationResourcesByMotivationForCanvas, getCanvasLabel, getSelectedAnnotationId, + getConfig, } from '../state/selectors'; import { CanvasAnnotations } from '../components/CanvasAnnotations'; @@ -28,10 +29,10 @@ function getIdAndContentOfResources(resources) { const mapStateToProps = (state, { canvasId, windowId }) => ({ annotations: getIdAndContentOfResources( getAnnotationResourcesByMotivationForCanvas( - state, { canvasId, motivations: state.config.annotations.filteredMotivations, windowId }, + state, { canvasId, windowId }, ), ), - htmlSanitizationRuleSet: state.config.annotations.htmlSanitizationRuleSet, + htmlSanitizationRuleSet: getConfig(state).annotations.htmlSanitizationRuleSet, label: getCanvasLabel(state, { canvasId, windowId, diff --git a/src/containers/ChangeThemeDialog.js b/src/containers/ChangeThemeDialog.js index b992922c008b67f2445c23443d197414b4fe32d9..ade8c4eeebe49ebeb76b720f099b1ec4be4863c6 100644 --- a/src/containers/ChangeThemeDialog.js +++ b/src/containers/ChangeThemeDialog.js @@ -4,7 +4,7 @@ import { withStyles } from '@material-ui/core'; import { withTranslation } from 'react-i18next'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; -import { getThemeIds } from '../state/selectors'; +import { getThemeIds, getConfig } from '../state/selectors'; import { ChangeThemeDialog } from '../components/ChangeThemeDialog'; /** @@ -22,7 +22,7 @@ const mapDispatchToProps = (dispatch, { windowId }) => ({ * @private */ const mapStateToProps = state => ({ - selectedTheme: state.config.selectedTheme, + selectedTheme: getConfig(state).selectedTheme, themeIds: getThemeIds(state), }); diff --git a/src/containers/ErrorContent.js b/src/containers/ErrorContent.js index c6b864cc280156ac2abbef5500907af54e1a247d..951d3e20158177d5b3a148e163da9e513b448839 100644 --- a/src/containers/ErrorContent.js +++ b/src/containers/ErrorContent.js @@ -9,6 +9,7 @@ import { getManifest, getWindow, getViewer, + getConfig, } from '../state/selectors'; /** mapStateToProps */ @@ -19,7 +20,7 @@ const mapStateToProps = (state, { companionWindowId, windowId }) => ({ viewer: getViewer(state, { windowId }), window: getWindow(state, { windowId }), }, - showJsError: state.config.window.showJsError, + showJsError: getConfig(state).window.showJsError, }); /** diff --git a/src/containers/ErrorDialog.js b/src/containers/ErrorDialog.js index 48fbea86332541a0635a0dc33bdc2625a07a32cd..9aeaaa578fab376af5b5a6095c493be608d760b5 100644 --- a/src/containers/ErrorDialog.js +++ b/src/containers/ErrorDialog.js @@ -1,14 +1,10 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import { withTranslation } from 'react-i18next'; -import { - first, - omit, - values, -} from 'lodash'; import { withPlugins } from '../extend/withPlugins'; import { ErrorDialog } from '../components/ErrorDialog'; import * as actions from '../state/actions'; +import { getLatestError } from '../state/selectors'; /** * mapStateToProps - to hook up connect @@ -16,8 +12,7 @@ import * as actions from '../state/actions'; * @private */ const mapStateToProps = state => ({ - /* extract 'items' value and get first key-value-pair (an error) */ - error: first(values(omit(state.errors, 'items'))), + error: getLatestError(state), }); /** diff --git a/src/containers/GalleryViewThumbnail.js b/src/containers/GalleryViewThumbnail.js index 268bc7e316b4e4e3826556eb83101dfcea399f00..ade22c819931935bc40c4f0e1fa93d59cbeb1c8a 100644 --- a/src/containers/GalleryViewThumbnail.js +++ b/src/containers/GalleryViewThumbnail.js @@ -8,6 +8,7 @@ import { getSearchAnnotationsForWindow, getSelectedContentSearchAnnotationIds, getCurrentCanvas, + getConfig, } from '../state/selectors'; /** @@ -70,7 +71,7 @@ const mapStateToProps = (state, { canvas, windowId }) => { return { annotationsCount: canvasAnnotations.length, annotationSelected: canvasAnnotations.some(a => selectedAnnotationIds.includes(a.id)), - config: state.config.galleryView, + config: getConfig(state).galleryView, selected: currentCanvas && currentCanvas.id === canvas.id, }; }; diff --git a/src/containers/MinimalWindow.js b/src/containers/MinimalWindow.js index 20351e144ade292a3c697df3e71516433cf0abe1..b0e4974bcbd8e50c8758b70a83c568291cac5f3a 100644 --- a/src/containers/MinimalWindow.js +++ b/src/containers/MinimalWindow.js @@ -5,11 +5,12 @@ import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; import { MinimalWindow } from '../components/MinimalWindow'; +import { getWindowConfig } from '../state/selectors'; /** mapStateToProps */ const mapStateToProps = (state, { windowId }) => ({ - allowClose: state.config.window.allowClose, - allowWindowSideBar: state.config.window.allowWindowSideBar, + allowClose: getWindowConfig(state, { windowId }).allowClose, + allowWindowSideBar: getWindowConfig(state, { windowId }).allowWindowSideBar, }); /** diff --git a/src/containers/OpenSeadragonViewer.js b/src/containers/OpenSeadragonViewer.js index 25bd53f6f5b91070af23e2c9f642ec9733dc4e05..7b3fc142dc077d905fe401ab3f2b44df915f14a0 100644 --- a/src/containers/OpenSeadragonViewer.js +++ b/src/containers/OpenSeadragonViewer.js @@ -43,7 +43,7 @@ const mapStateToProps = (state, { windowId }) => { && infoResponse.isFetching === false && infoResponse.error === undefined)), nonTiledImages: getVisibleCanvasNonTiledResources(state, { windowId }), - osdConfig: state.config.osdConfig, + osdConfig: getConfig(state).osdConfig, viewerConfig: getViewer(state, { windowId }), }; }; diff --git a/src/containers/SidebarIndexThumbnail.js b/src/containers/SidebarIndexThumbnail.js index f357f5e040e6aa4c7eca0ce4139c31f9f37eb6c8..9f582dc4647693cfb9d78d574648bd4169f066d1 100644 --- a/src/containers/SidebarIndexThumbnail.js +++ b/src/containers/SidebarIndexThumbnail.js @@ -4,6 +4,7 @@ import { withTranslation } from 'react-i18next'; import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; import { SidebarIndexThumbnail } from '../components/SidebarIndexThumbnail'; +import { getConfig } from '../state/selectors'; /** * mapStateToProps - used to hook up state to props @@ -11,7 +12,7 @@ import { SidebarIndexThumbnail } from '../components/SidebarIndexThumbnail'; * @private */ const mapStateToProps = (state, { data }) => ({ - config: state.config, + ...(getConfig(state).canvasNavigation || {}), }); /** diff --git a/src/containers/ThumbnailNavigation.js b/src/containers/ThumbnailNavigation.js index df0e69e15228cd613c99d8e61eec331a62759d6a..eb82793df90cd4a35c6432dfe4b9a59d339bd6cb 100644 --- a/src/containers/ThumbnailNavigation.js +++ b/src/containers/ThumbnailNavigation.js @@ -7,9 +7,10 @@ import CanvasGroupings from '../lib/CanvasGroupings'; import * as actions from '../state/actions'; import { ThumbnailNavigation } from '../components/ThumbnailNavigation'; import { + getCompanionWindow, getWindow, getNextCanvasGrouping, getPreviousCanvasGrouping, getCanvases, getCanvasIndex, getWindowViewType, - getSequenceViewingDirection, + getSequenceViewingDirection, getConfig, } from '../state/selectors'; /** @@ -27,8 +28,10 @@ const mapStateToProps = (state, { windowId }) => { canvasIndex: getCanvasIndex(state, { windowId }), hasNextCanvas: !!getNextCanvasGrouping(state, { windowId }), hasPreviousCanvas: !!getPreviousCanvasGrouping(state, { windowId }), - position: state.companionWindows[state.windows[windowId].thumbnailNavigationId].position, - thumbnailNavigation: state.config.thumbnailNavigation, + position: getCompanionWindow(state, { + companionWindowId: getWindow(state, { windowId }).thumbnailNavigationId, + }).position, + thumbnailNavigation: getConfig(state).thumbnailNavigation, view: viewType, viewingDirection: getSequenceViewingDirection(state, { windowId }), }; diff --git a/src/containers/WindowCanvasNavigationControls.js b/src/containers/WindowCanvasNavigationControls.js index 0f8bf427eb580913276b3a7898955451d0fda169..7fc436c929e8d50381518c2b1be502de884e001e 100644 --- a/src/containers/WindowCanvasNavigationControls.js +++ b/src/containers/WindowCanvasNavigationControls.js @@ -7,6 +7,7 @@ import { withPlugins } from '../extend/withPlugins'; import { getCurrentCanvas, getCanvasLabel, + getWorkspace, } from '../state/selectors'; import { WindowCanvasNavigationControls } from '../components/WindowCanvasNavigationControls'; @@ -16,7 +17,7 @@ const mapStateToProps = (state, { windowId }) => ({ canvasId: (getCurrentCanvas(state, { windowId }) || {}).id, windowId, }), - visible: state.workspace.focusedWindowId === windowId, + visible: getWorkspace(state).focusedWindowId === windowId, }); /** diff --git a/src/containers/WindowList.js b/src/containers/WindowList.js index 2e76904ed3780c4aff4eca6c07c811a09ed9aa78..0d8f32c4d4d0394eca87db97c00ca0528a626384 100644 --- a/src/containers/WindowList.js +++ b/src/containers/WindowList.js @@ -3,7 +3,7 @@ import { compose } from 'redux'; import { withTranslation } from 'react-i18next'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; -import { getContainerId, getWindowTitles } from '../state/selectors'; +import { getContainerId, getWindowIds, getWindowTitles } from '../state/selectors'; import { WindowList } from '../components/WindowList'; /** @@ -24,7 +24,7 @@ const mapStateToProps = state => ( { containerId: getContainerId(state), titles: getWindowTitles(state), - windowIds: Object.keys(state.windows), + windowIds: getWindowIds(state), } ); diff --git a/src/containers/WindowListButton.js b/src/containers/WindowListButton.js index f35f162ca27446079efe1a5260d23ff9284552a0..74506d9dbaec7e421ae6288d6eb93e64210aeaec 100644 --- a/src/containers/WindowListButton.js +++ b/src/containers/WindowListButton.js @@ -3,12 +3,13 @@ import { connect } from 'react-redux'; import { withTranslation } from 'react-i18next'; import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; +import { getWindowIds, getWorkspace } from '../state/selectors'; import { WindowListButton } from '../components/WindowListButton'; /** */ -const mapStateToProps = ({ windows, workspace }) => ({ - disabled: workspace.isWorkspaceAddVisible, - windowCount: Object.keys(windows).length, +const mapStateToProps = (state) => ({ + disabled: getWorkspace(state).isWorkspaceAddVisible, + windowCount: getWindowIds(state).length, }); /** diff --git a/src/containers/WindowSideBarAnnotationsPanel.js b/src/containers/WindowSideBarAnnotationsPanel.js index 64af2caedc2d7bf2977b00037c2296865eefb012..854f5175882ed828f177d762ef796abd6a4f7bab 100644 --- a/src/containers/WindowSideBarAnnotationsPanel.js +++ b/src/containers/WindowSideBarAnnotationsPanel.js @@ -17,7 +17,7 @@ import { WindowSideBarAnnotationsPanel } from '../components/WindowSideBarAnnota const mapStateToProps = (state, { windowId }) => ({ annotationCount: getAnnotationResourcesByMotivation( state, - { motivations: state.config.annotations.filteredMotivations, windowId }, + { windowId }, ).length, selectedCanvases: getVisibleCanvases(state, { windowId }), }); diff --git a/src/containers/WindowSideBarButtons.js b/src/containers/WindowSideBarButtons.js index 4b08c5f80429656f064da8aeda5925b0efebcb22..a8f70f97d1704ff54c9471d0fd0f9b5726e65aa8 100644 --- a/src/containers/WindowSideBarButtons.js +++ b/src/containers/WindowSideBarButtons.js @@ -41,7 +41,7 @@ function hasLayers(canvases) { const mapStateToProps = (state, { windowId }) => ({ hasAnnotations: getAnnotationResourcesByMotivation( state, - { motivations: state.config.annotations.filteredMotivations, windowId }, + { windowId }, ).length > 0, hasAnyLayers: hasLayers(getCanvases(state, { windowId })), hasCurrentLayers: hasLayers(getVisibleCanvases(state, { windowId })), diff --git a/src/containers/WindowSideBarInfoPanel.js b/src/containers/WindowSideBarInfoPanel.js index 54652a14a1ee6748c39ab68ec777012227d420d0..812dfad47ee483978bb62c1ef08746fda2f01250 100644 --- a/src/containers/WindowSideBarInfoPanel.js +++ b/src/containers/WindowSideBarInfoPanel.js @@ -5,6 +5,7 @@ import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; import { + getCompanionWindow, getManifestLocale, getMetadataLocales, getVisibleCanvases, @@ -19,7 +20,8 @@ import { WindowSideBarInfoPanel } from '../components/WindowSideBarInfoPanel'; */ const mapStateToProps = (state, { id, windowId }) => ({ availableLocales: getMetadataLocales(state, { companionWindowId: id, windowId }), - locale: state.companionWindows[id].locale || getManifestLocale(state, { windowId }), + locale: getCompanionWindow(state, { companionWindowId: id }).locale + || getManifestLocale(state, { windowId }), selectedCanvases: getVisibleCanvases(state, { windowId }), showLocalePicker: getWindowConfig(state, { windowId }).showLocalePicker, }); diff --git a/src/containers/WindowTopBar.js b/src/containers/WindowTopBar.js index bfb5ffdbeb48fbc9f87c351d4b01b7440d0a98f8..eebf13ebba6d2ceb07444f58b0937d207dd4d7d9 100644 --- a/src/containers/WindowTopBar.js +++ b/src/containers/WindowTopBar.js @@ -4,7 +4,7 @@ import { withTranslation } from 'react-i18next'; import { withStyles } from '@material-ui/core'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; -import { getWindowConfig } from '../state/selectors'; +import { getWindowConfig, isFocused } from '../state/selectors'; import { WindowTopBar } from '../components/WindowTopBar'; /** mapStateToProps */ @@ -17,7 +17,7 @@ const mapStateToProps = (state, { windowId }) => { allowMaximize: config.allowMaximize, allowTopMenuButton: config.allowTopMenuButton, allowWindowSideBar: config.allowWindowSideBar, - focused: state.workspace.focusedWindowId === windowId, + focused: isFocused(state, { windowId }), maximized: config.maximized, }; }; diff --git a/src/containers/Workspace.js b/src/containers/Workspace.js index 63737d8d15756d361d76e3af76a8fadb1d1359ad..6eac59013b39931e67ce89e965cb58bfa3678c7b 100644 --- a/src/containers/Workspace.js +++ b/src/containers/Workspace.js @@ -4,7 +4,10 @@ import { withTranslation } from 'react-i18next'; import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; import { Workspace } from '../components/Workspace'; -import { getMaximizedWindowsIds, getWindowIds, getWorkspaceType } from '../state/selectors'; +import { + getMaximizedWindowsIds, getWindowIds, getWorkspaceType, + getConfig, getWorkspace, +} from '../state/selectors'; import * as actions from '../state/actions'; /** @@ -14,11 +17,11 @@ import * as actions from '../state/actions'; */ const mapStateToProps = state => ( { - allowNewWindows: state.config.workspace.allowNewWindows, - isWorkspaceControlPanelVisible: state.config.workspaceControlPanel.enabled, + allowNewWindows: getConfig(state).workspace.allowNewWindows, + isWorkspaceControlPanelVisible: getConfig(state).workspaceControlPanel.enabled, maximizedWindowIds: getMaximizedWindowsIds(state), windowIds: getWindowIds(state), - workspaceId: state.workspace.id, + workspaceId: getWorkspace(state).id, workspaceType: getWorkspaceType(state), } ); diff --git a/src/containers/WorkspaceAdd.js b/src/containers/WorkspaceAdd.js index 9fd38e4023581ce98bf10f1e70309e8bbff0fa66..fe7b6c68232c39a5a2e9a4f7e815cf198335f288 100644 --- a/src/containers/WorkspaceAdd.js +++ b/src/containers/WorkspaceAdd.js @@ -5,13 +5,14 @@ import { withStyles } from '@material-ui/core'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; import { WorkspaceAdd } from '../components/WorkspaceAdd'; +import { getCatalog } from '../state/selectors'; /** * mapStateToProps - to hook up connect * @memberof Workspace * @private */ -const mapStateToProps = state => ({ catalog: state.catalog }); +const mapStateToProps = state => ({ catalog: getCatalog(state) }); /** * mapDispatchToProps - used to hook up connect to action creators diff --git a/src/containers/WorkspaceAddButton.js b/src/containers/WorkspaceAddButton.js index 4635e30a04227fcf912f3fd3d97479853c1d1464..642e83fc482c9fbcf3404c6d35fb4701e4d48c10 100644 --- a/src/containers/WorkspaceAddButton.js +++ b/src/containers/WorkspaceAddButton.js @@ -5,6 +5,7 @@ import { withStyles } from '@material-ui/core'; import withWidth from '@material-ui/core/withWidth'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; +import { getWindowIds, getWorkspace } from '../state/selectors'; import { WorkspaceAddButton } from '../components/WorkspaceAddButton'; /** @@ -13,13 +14,13 @@ import { WorkspaceAddButton } from '../components/WorkspaceAddButton'; * @private */ const mapStateToProps = (state, { width }) => { - const { isWorkspaceAddVisible } = state.workspace; + const { isWorkspaceAddVisible } = getWorkspace(state); return { isWorkspaceAddVisible, useExtendedFab: ( (width !== 'xs') && !isWorkspaceAddVisible - && Object.keys(state.windows).length === 0 + && getWindowIds(state).length === 0 ), }; }; diff --git a/src/containers/WorkspaceElasticWindow.js b/src/containers/WorkspaceElasticWindow.js index 8da97f482f2f181def882dfecdc71584e723ee3f..e619b40e388e61baa51c2565978aa8326739a08c 100644 --- a/src/containers/WorkspaceElasticWindow.js +++ b/src/containers/WorkspaceElasticWindow.js @@ -3,7 +3,10 @@ import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core'; import * as actions from '../state/actions'; import WorkspaceElasticWindow from '../components/WorkspaceElasticWindow'; -import { selectCompanionWindowDimensions } from '../state/selectors'; +import { + selectCompanionWindowDimensions, getWorkspace, isFocused, + getElasticLayout, +} from '../state/selectors'; /** * mapStateToProps - to hook up connect @@ -13,9 +16,9 @@ import { selectCompanionWindowDimensions } from '../state/selectors'; const mapStateToProps = (state, { windowId }) => ( { companionWindowDimensions: selectCompanionWindowDimensions(state, { windowId }), - focused: state.workspace.focusedWindowId === windowId, - layout: state.elasticLayout[windowId], - workspace: state.workspace, + focused: isFocused(state, { windowId }), + layout: getElasticLayout(state)[windowId], + workspace: getWorkspace(state), } ); diff --git a/src/containers/WorkspaceMenu.js b/src/containers/WorkspaceMenu.js index dcf19e1e512f7063ba27b0e81ecbe1fc53206f54..afc303cfc921a212d52c8d5b9bcbc75b5a9b6398 100644 --- a/src/containers/WorkspaceMenu.js +++ b/src/containers/WorkspaceMenu.js @@ -3,7 +3,10 @@ import { connect } from 'react-redux'; import { withTranslation } from 'react-i18next'; import { withPlugins } from '../extend/withPlugins'; import * as actions from '../state/actions'; -import { getContainerId, getShowZoomControlsConfig, getThemeIds } from '../state/selectors'; +import { + getContainerId, getShowZoomControlsConfig, getThemeIds, + getWorkspace, +} from '../state/selectors'; import { WorkspaceMenu } from '../components/WorkspaceMenu'; /** @@ -22,7 +25,7 @@ const mapDispatchToProps = { */ const mapStateToProps = state => ({ containerId: getContainerId(state), - isWorkspaceAddVisible: state.workspace.isWorkspaceAddVisible, + isWorkspaceAddVisible: getWorkspace(state).isWorkspaceAddVisible, showThemePicker: getThemeIds(state).length > 0, showZoomControls: getShowZoomControlsConfig(state), }); diff --git a/src/containers/WorkspaceMosaic.js b/src/containers/WorkspaceMosaic.js index 3afb145675dbadb48ab415e2de267ded1e2406b1..46ad650e49c1f2d45194f1df837a21812e34f1db 100644 --- a/src/containers/WorkspaceMosaic.js +++ b/src/containers/WorkspaceMosaic.js @@ -2,6 +2,7 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; +import { getWorkspace } from '../state/selectors'; import * as actions from '../state/actions'; import { WorkspaceMosaic } from '../components/WorkspaceMosaic'; @@ -12,9 +13,9 @@ import { WorkspaceMosaic } from '../components/WorkspaceMosaic'; */ const mapStateToProps = state => ( { - layout: state.workspace.layout, - windows: state.windows, - workspaceId: state.workspace.id, + layout: getWorkspace(state).layout, + windowIds: getWorkspace(state).windowIds, + workspaceId: getWorkspace(state).id, } ); diff --git a/src/state/actions/window.js b/src/state/actions/window.js index 7880dbace709124c7c4a5e85063f428b4b70e1ec..6ba1a236967c8d934b0af8718e0497cbca89e281 100644 --- a/src/state/actions/window.js +++ b/src/state/actions/window.js @@ -1,5 +1,6 @@ import { v4 as uuid } from 'uuid'; import ActionTypes from './action-types'; +import { miradorSlice } from '../selectors/utils'; /** * focusWindow - action creator @@ -23,8 +24,8 @@ export function focusWindow(windowId, pan = false) { */ export function addWindow({ companionWindows, manifest, ...options }) { return (dispatch, getState) => { - const { config, windows } = getState(); - const numWindows = Object.keys(windows).length; + const { config, workspace: { windowIds = [] } } = miradorSlice(getState()); + const numWindows = windowIds.length; const windowId = options.id || `window-${uuid()}`; const cwThumbs = `cw-${uuid()}`; @@ -63,7 +64,6 @@ export function addWindow({ companionWindows, manifest, ...options }) { draggingEnabled: true, highlightAllAnnotations: config.window.highlightAllAnnotations || false, id: windowId, - layoutOrder: numWindows + 1, manifestId: null, maximized: false, rangeId: null, diff --git a/src/state/createStore.js b/src/state/createStore.js index b4c76bda46f210a10c85fb6b22a95aad21db1fa2..158f359708c1811f8c09600349302e13863b6408 100644 --- a/src/state/createStore.js +++ b/src/state/createStore.js @@ -5,10 +5,11 @@ import thunkMiddleware from 'redux-thunk'; import createSagaMiddleware from 'redux-saga'; -import { createStore, applyMiddleware } from 'redux'; +import { combineReducers, createStore, applyMiddleware } from 'redux'; import { composeWithDevTools } from 'redux-devtools-extension'; import createRootReducer from './reducers/rootReducer'; import getRootSaga from './sagas'; +import settings from '../config/settings'; // create the saga middleware const sagaMiddleware = createSagaMiddleware(); @@ -17,8 +18,14 @@ const sagaMiddleware = createSagaMiddleware(); * Configure Store */ export default function (pluginReducers, pluginSagas = []) { + const miradorReducer = createRootReducer(pluginReducers); + + const rootReducer = settings.state.slice + ? combineReducers({ [settings.state.slice]: miradorReducer }) + : miradorReducer; + const store = createStore( - createRootReducer(pluginReducers), + rootReducer, composeWithDevTools( applyMiddleware( thunkMiddleware, sagaMiddleware, diff --git a/src/state/sagas/app.js b/src/state/sagas/app.js index de44905423376b3cc3b5a212f2d502fe34e2a981..ca8bd44d6027775407d475f75cafa78a3c8a6ad6 100644 --- a/src/state/sagas/app.js +++ b/src/state/sagas/app.js @@ -23,14 +23,13 @@ export function* importConfig({ config: { thumbnailNavigation, windows } }) { if (!windows || windows.length === 0) return; const thunks = yield all( - windows.map((miradorWindow, layoutOrder) => { + windows.map((miradorWindow) => { const windowId = `window-${uuid()}`; const manifestId = miradorWindow.manifestId || miradorWindow.loadedManifest; return call(addWindow, { // these are default values ... id: windowId, - layoutOrder, manifestId, thumbnailNavigationPosition: thumbnailNavigation && thumbnailNavigation.defaultPosition, // ... overridden by values from the window configuration ... diff --git a/src/state/selectors/annotations.js b/src/state/selectors/annotations.js index d8849b6df12b5d1256aef424ac1b4b4a4b9ee20c..6a86baa4faac3c936e9eb683ebef1f62feff26c2 100644 --- a/src/state/selectors/annotations.js +++ b/src/state/selectors/annotations.js @@ -2,10 +2,21 @@ import { createSelector } from 'reselect'; import filter from 'lodash/filter'; import flatten from 'lodash/flatten'; import AnnotationFactory from '../../lib/AnnotationFactory'; +import { miradorSlice } from './utils'; import { getCanvas, getVisibleCanvasIds } from './canvases'; +import { getConfig } from './config'; +import { getWindow } from './getters'; /** */ -export const getAnnotations = state => state.annotations; +export const getAnnotations = state => miradorSlice(state).annotations; + +const getMotiviation = createSelector( + [ + getConfig, + (state, { motivations }) => motivations, + ], + (config, motivations) => motivations || config.annotations.filteredMotivations, +); const getAnnotationsOnCanvas = createSelector( [ @@ -34,7 +45,7 @@ const getPresentAnnotationsCanvas = createSelector( const getAnnotationsOnSelectedCanvases = createSelector( [ getVisibleCanvasIds, - state => state.annotations, + getAnnotations, ], (canvasIds, annotations) => { if (!annotations || canvasIds.length === 0) return []; @@ -66,7 +77,7 @@ export const getPresentAnnotationsOnSelectedCanvases = createSelector( export const getAnnotationResourcesByMotivationForCanvas = createSelector( [ getPresentAnnotationsCanvas, - (state, { motivations }) => motivations, + getMotiviation, ], (annotations, motivations) => filter( flatten(annotations.map(annotation => annotation.resources)), @@ -85,7 +96,7 @@ export const getAnnotationResourcesByMotivationForCanvas = createSelector( export const getAnnotationResourcesByMotivation = createSelector( [ getPresentAnnotationsOnSelectedCanvases, - (state, { motivations }) => motivations, + getMotiviation, ], (annotations, motivations) => filter( flatten(annotations.map(annotation => annotation.resources)), @@ -104,9 +115,9 @@ export const getAnnotationResourcesByMotivation = createSelector( */ export const getSelectedAnnotationId = createSelector( [ - (state, { windowId }) => state.windows[windowId].selectedAnnotationId, + getWindow, ], - selectedAnnotationId => selectedAnnotationId, + ({ selectedAnnotationId }) => selectedAnnotationId, ); export const getSelectedAnnotationsOnCanvases = createSelector( diff --git a/src/state/selectors/auth.js b/src/state/selectors/auth.js index abe9ca55da6482db8bcec08d57e372eaa596f3ee..c1d75341d9e3bc86a3d02aef9c074f24730b3964 100644 --- a/src/state/selectors/auth.js +++ b/src/state/selectors/auth.js @@ -1,2 +1,7 @@ +import { miradorSlice } from './utils'; + /** */ -export const getAccessTokens = state => state.accessTokens; +export const getAccessTokens = state => miradorSlice(state).accessTokens || {}; + +/** */ +export const getAuth = state => miradorSlice(state).auth || {}; diff --git a/src/state/selectors/canvases.js b/src/state/selectors/canvases.js index 2250708332ec21d318bbebdb2eb2814187a9b540..d2083c184f89a5e46e2ec4afb0633ef399ec2b2b 100644 --- a/src/state/selectors/canvases.js +++ b/src/state/selectors/canvases.js @@ -3,10 +3,14 @@ import { Utils } from 'manifesto.js/dist-esmodule/Utils'; import flatten from 'lodash/flatten'; import CanvasGroupings from '../../lib/CanvasGroupings'; import MiradorCanvas from '../../lib/MiradorCanvas'; +import { miradorSlice } from './utils'; import { getWindow } from './getters'; import { getSequence } from './sequences'; import { getWindowViewType } from './windows'; +/** */ +export const selectInfoResponses = state => miradorSlice(state).infoResponses; + export const getCanvases = createSelector( [getSequence], sequence => (sequence && sequence.getCanvases()) || [], @@ -172,9 +176,6 @@ export const getCanvasDescription = createSelector( canvas => canvas && canvas.getProperty('description'), ); -/** */ -export const selectInfoResponses = state => state.infoResponses; - export const getVisibleCanvasNonTiledResources = createSelector( [ getVisibleCanvases, diff --git a/src/state/selectors/companionWindows.js b/src/state/selectors/companionWindows.js index 692ee92295682ff930f857846055bbda0370c6ea..914c73a2f0148bd3d4e285e558d9397cd234332b 100644 --- a/src/state/selectors/companionWindows.js +++ b/src/state/selectors/companionWindows.js @@ -1,12 +1,21 @@ import { createSelector } from 'reselect'; import groupBy from 'lodash/groupBy'; +import { miradorSlice } from './utils'; import { getWindow, getWindows } from './getters'; /** */ export function getCompanionWindows(state) { - return state.companionWindows; + return miradorSlice(state).companionWindows || {}; } +export const getCompanionWindow = createSelector( + [ + getCompanionWindows, + (state, { companionWindowId }) => companionWindowId, + ], + (companionWindows, companionWindowId) => companionWindowId && companionWindows[companionWindowId], +); + /** Return position of thumbnail navigation in a certain window. * @param {object} state * @param {String} windowId @@ -15,7 +24,7 @@ export function getCompanionWindows(state) { export const getThumbnailNavigationPosition = createSelector( [ getWindow, - state => state.companionWindows, + getCompanionWindows, ], (window, companionWindows) => window && companionWindows[window.thumbnailNavigationId] @@ -59,14 +68,6 @@ const getCompanionWindowsByWindowAndPosition = createSelector( ), ); -export const getCompanionWindow = createSelector( - [ - getCompanionWindows, - (state, { companionWindowId }) => companionWindowId, - ], - (companionWindows, companionWindowId) => companionWindows[companionWindowId], -); - /** * Return companion windows of a window * @param {String} windowId diff --git a/src/state/selectors/config.js b/src/state/selectors/config.js index 29c7e3efe759040135d6860dd2e78071a9be694d..63ac28467fec86c6550b5c1e2eb3c7dea7515bbd 100644 --- a/src/state/selectors/config.js +++ b/src/state/selectors/config.js @@ -1,9 +1,12 @@ import { createSelector } from 'reselect'; import deepmerge from 'deepmerge'; +import { miradorSlice } from './utils'; +import { getWorkspace } from './getters'; /** */ export function getConfig(state) { - return (state && state.config) || {}; + const slice = miradorSlice(state || {}); + return slice.config || {}; } /** @@ -49,7 +52,7 @@ export const getLanguagesFromConfigWithCurrent = createSelector( export const getShowZoomControlsConfig = createSelector( [ - state => state.workspace, + getWorkspace, getConfig, ], (workspace, config) => ( diff --git a/src/state/selectors/getters.js b/src/state/selectors/getters.js index b8db261fbb61f732188bacf8eee40e315aa4f36a..066dce47da749c50eaa42e10ae5e619a9f3b8d19 100644 --- a/src/state/selectors/getters.js +++ b/src/state/selectors/getters.js @@ -1,4 +1,5 @@ import { createSelector } from 'reselect'; +import { miradorSlice } from './utils'; /** * Return the manifest titles for all open windows @@ -6,12 +7,12 @@ import { createSelector } from 'reselect'; * @return {object} */ export function getWindowManifests(state) { - return Object.values(state.windows).map(window => window.manifestId); + return Object.values(miradorSlice(state).windows).map(window => window.manifestId); } /** */ export function getWindows(state) { - return state.windows || {}; + return miradorSlice(state).windows || {}; } /** */ @@ -21,21 +22,26 @@ export function getWindow(state, { windowId }) { export const getViewer = createSelector( [ - state => state.viewers, + state => miradorSlice(state).viewers, (state, { windowId }) => windowId, ], (viewers, windowId) => viewers[windowId], ); +/** */ +export function getWorkspace(state) { + return miradorSlice(state).workspace; +} + /** */ export const getWindowIds = createSelector( - [getWindows], - windows => Object.keys(windows), + [getWorkspace], + ({ windowIds }) => windowIds || [], ); /** */ export function getManifests(state) { - return state.manifests || {}; + return miradorSlice(state).manifests || {}; } /** Get the relevant manifest information */ @@ -46,3 +52,8 @@ export function getManifest(state, { manifestId, windowId }) { || (windowId && (getWindow(state, { windowId }) || {}).manifestId) ]; } + +/** */ +export function getCatalog(state) { + return miradorSlice(state).catalog || {}; +} diff --git a/src/state/selectors/index.js b/src/state/selectors/index.js index a25c81f7eaee83eada4b8aa9e0e2f1e7aadb2773..866141957ffc7875f157663ae457c88428e38c5b 100644 --- a/src/state/selectors/index.js +++ b/src/state/selectors/index.js @@ -11,3 +11,4 @@ export * from './ranges'; export * from './layers'; export * from './sequences'; export * from './auth'; +export * from './utils'; diff --git a/src/state/selectors/layers.js b/src/state/selectors/layers.js index c1c3913bfd1a16dd01b3c12e68915f2050b8d739..e75c165b7e2c195c9153d2c87f030b38b72ed127 100644 --- a/src/state/selectors/layers.js +++ b/src/state/selectors/layers.js @@ -1,6 +1,7 @@ import { createSelector } from 'reselect'; import MiradorCanvas from '../../lib/MiradorCanvas'; import { getCanvas, getVisibleCanvasIds } from './canvases'; +import { miradorSlice } from './utils'; /** * Get the image layers from a canvas @@ -21,7 +22,7 @@ export const getCanvasLayers = createSelector( */ export const getLayers = createSelector( [ - state => state.layers || {}, + state => miradorSlice(state).layers || {}, (state, { windowId }) => windowId, (state, { canvasId }) => canvasId, ], diff --git a/src/state/selectors/manifests.js b/src/state/selectors/manifests.js index 3c205cfce5a50d301bfd549dacf38e46cc9c343b..8b8d02bce111db0f52708a50d962f4dd2cb27407 100644 --- a/src/state/selectors/manifests.js +++ b/src/state/selectors/manifests.js @@ -3,7 +3,9 @@ import createCachedSelector from 're-reselect'; import { LanguageMap } from 'manifesto.js/dist-esmodule/LanguageMap'; import { Utils } from 'manifesto.js/dist-esmodule/Utils'; import getThumbnail from '../../lib/ThumbnailFactory'; +import { getCompanionWindow } from './companionWindows'; import { getManifest } from './getters'; +import { getConfig } from './config'; /** */ function createManifestoInstance(json, locale) { @@ -11,6 +13,17 @@ function createManifestoInstance(json, locale) { return Utils.parseManifest(json, locale ? { locale } : undefined); } +/** */ +const getLocale = createSelector( + [ + getCompanionWindow, + getConfig, + ], + (companionWindow = {}, config = {}) => ( + companionWindow.locale || config.language + ), +); + /** Convenience selector to get a manifest (or placeholder) */ export const getManifestStatus = createSelector( [getManifest], @@ -30,13 +43,10 @@ const getContextualManifestoInstance = createCachedSelector( (manifest, locale) => manifest && createManifestoInstance(manifest.json, locale), )( - (state, props) => [ - props.manifestId, - props.windowId, - (state.companionWindows - && state.companionWindows[props.companionWindowId] - && state.companionWindows[props.companionWindowId].locale) - || (state.config && state.config.language), + (state, { companionWindowId, manifestId, windowId }) => [ + manifestId, + windowId, + getLocale(state, { companionWindowId }), ].join(' - '), // Cache key consisting of manifestId, windowId, and locale ); @@ -63,13 +73,6 @@ function getProperty(property) { ); } -/** */ -function getLocale(state, { companionWindowId }) { - return (companionWindowId - && state.companionWindows[companionWindowId] - && state.companionWindows[companionWindowId].locale) - || (state.config && state.config.language); -} /** * Get the logo for a manifest * @param {object} state diff --git a/src/state/selectors/searches.js b/src/state/selectors/searches.js index 97dd793c3a540e6ab67107aaad8cbc7603e8af2e..b65ad89351d353b987ec73bfdbcd81d0195ab8e0 100644 --- a/src/state/selectors/searches.js +++ b/src/state/selectors/searches.js @@ -5,11 +5,15 @@ import AnnotationList from '../../lib/AnnotationList'; import { getCanvas, getCanvases } from './canvases'; import { getWindow } from './getters'; import { getManifestLocale } from './manifests'; +import { miradorSlice } from './utils'; + +/** Get searches from state */ +const getSearches = (state) => miradorSlice(state).searches; export const getSearchForWindow = createSelector( [ (state, { windowId }) => windowId, - state => state.searches, + getSearches, ], (windowId, searches) => { if (!windowId || !searches) return {}; diff --git a/src/state/selectors/utils.js b/src/state/selectors/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..22de1e7913643e2dd12f2cf1637ae9cc9c79aa11 --- /dev/null +++ b/src/state/selectors/utils.js @@ -0,0 +1,8 @@ +import settings from '../../config/settings'; + +/** */ +export function miradorSlice(state) { + if (settings.state.slice) return state[settings.state.slice]; + + return state; +} diff --git a/src/state/selectors/windows.js b/src/state/selectors/windows.js index d379f514ba5f394088c277ba3e4c137d108a1424..d2a5434f1bedc640db336374df4eb76dd1457efa 100644 --- a/src/state/selectors/windows.js +++ b/src/state/selectors/windows.js @@ -3,14 +3,14 @@ import { getManifestTitle, } from './manifests'; import { getConfig } from './config'; -import { getWindows, getWindow } from './getters'; +import { getWindows, getWindow, getWindowIds } from './getters'; import { getWorkspaceType } from './workspace'; import { getSequenceViewingHint, getSequenceBehaviors } from './sequences'; /** */ export const getWindowConfig = createSelector( [getConfig, getWindow], - ({ window: defaultConfig }, windowConfig) => ({ ...defaultConfig, ...windowConfig }), + ({ window: defaultConfig }, windowConfig = {}) => ({ ...defaultConfig, ...windowConfig }), ); /** @@ -92,7 +92,7 @@ export const getWindowDraggability = createSelector( [ getWorkspaceType, getWindow, - state => Object.keys(state.windows).length > 1, + state => getWindowIds(state).length > 1, ], (workspaceType, window, manyWindows) => { if (workspaceType === 'elastic') return true; diff --git a/src/state/selectors/workspace.js b/src/state/selectors/workspace.js index ba152f79f3699a8e060eb823ad3f312ddb064499..69d8ff62d632dcc9fa1c128c21e3ca3c7bc7a9df 100644 --- a/src/state/selectors/workspace.js +++ b/src/state/selectors/workspace.js @@ -1,13 +1,10 @@ import { createSelector } from 'reselect'; - -/** */ -export function getWorkspace(state) { - return state.workspace; -} +import { getWorkspace } from './getters'; +import { miradorSlice } from './utils'; /** */ export function getElasticLayout(state) { - return state.elasticLayout; + return miradorSlice(state).elasticLayout; } export const getFullScreenEnabled = createSelector( @@ -19,10 +16,22 @@ export const getFullScreenEnabled = createSelector( * @param {object} state */ export function getLatestError(state) { - return state.errors.items[0] && state.errors[state.errors.items[0]]; + const [errorId] = miradorSlice(state).errors.items; + + return miradorSlice(state).errors[errorId]; } export const getWorkspaceType = createSelector( [getWorkspace], ({ type }) => type, ); + +const getFocusedWindowId = createSelector( + [getWorkspace], + ({ focusedWindowId }) => focusedWindowId, +); + +/** Check if the current window is focused */ +export const isFocused = (state, { windowId }) => ( + getFocusedWindowId(state) === windowId +);