diff --git a/__tests__/integration/mirador/plugins/add.html b/__tests__/integration/mirador/plugins/add.html index d50ca8046d8881c31f8962580a8613104e4b3781..6b20517e0539609fe1d4647cf0daa60b1cf62425 100644 --- a/__tests__/integration/mirador/plugins/add.html +++ b/__tests__/integration/mirador/plugins/add.html @@ -72,7 +72,7 @@ mode: 'add', component: AddPluginComponentC, }; - + const wrapPluginD = { target: 'WindowTopBarPluginMenu', mode: 'wrap', diff --git a/__tests__/integration/mirador/plugins/companionWindow.html b/__tests__/integration/mirador/plugins/companionWindow.html new file mode 100644 index 0000000000000000000000000000000000000000..b87dfff5827b9d6b438cffad7e656e2a12da14f1 --- /dev/null +++ b/__tests__/integration/mirador/plugins/companionWindow.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html lang="en" dir="ltr"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <meta name="theme-color" content="#000000"> + <title>Mirador</title> + </head> + <body> + <div id="mirador" style="position: absolute; top: 0; bottom: 0; left: 0; right: 0;"></div> + <script src="../../../../node_modules/react/umd/react.development.js"></script> + <script src="../../../../node_modules/react-dom/umd/react-dom.development.js"></script> + <script>document.write("<script type='text/javascript' src='../../../../dist/mirador.min.js?v=" + Date.now() + "'><\/script>");</script> + <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> + <script type="text/babel"> + + const config = { id: 'mirador', windows: [{manifestId: 'https://purl.stanford.edu/hg676jb4964/iiif/manifest'}] }; + + const AddPluginComponentA = (props) => ( + <div id="add-plugin-companion-window"> + Plugin A + </div> + ); + + const AddPluginComponentB = ({TabButton, PluginComponents, dispatch, ...otherProps}) => ( + <TabButton id="add-plugin-companion-window-button" {...otherProps} label="P" value="pluginComponentA" /> + ); + + const addPluginA = { + target: 'CompanionWindowFactory', + companionWindowKey: 'pluginComponentA', + mode: 'add', + component: AddPluginComponentA, + }; + + const addPluginB = { + target: 'WindowSideBarButtons', + mode: 'add', + component: AddPluginComponentB, + }; + + const miradorInstance = Mirador.viewer(config, [addPluginA, addPluginB]); + + </script> + </body> +</html> diff --git a/__tests__/integration/mirador/plugins/companionWindow.test.js b/__tests__/integration/mirador/plugins/companionWindow.test.js new file mode 100644 index 0000000000000000000000000000000000000000..003fd12f84cd5df120d1fca9e897c0307a66d4bd --- /dev/null +++ b/__tests__/integration/mirador/plugins/companionWindow.test.js @@ -0,0 +1,19 @@ + +describe('add plugins for companion windows', () => { + beforeAll(async () => { + await page.goto('http://127.0.0.1:4488/__tests__/integration/mirador/plugins/companionWindow.html'); + await expect(page).toMatchElement('.mirador-viewer'); + await page.waitFor(1000); + }); + + it('added a plugin to the window sidebar and companion window', async () => { + await expect(page).toClick('button[aria-label="Toggle sidebar"]'); + + await page.waitFor(1000); + await expect(page).toMatchElement('.mirador-companion-window-left.mirador-window-sidebar-info-panel'); + await expect(page).toMatchElement('#add-plugin-companion-window-button'); + + await expect(page).toClick('#add-plugin-companion-window-button'); + await expect(page).toMatchElement('#add-plugin-companion-window'); + }); +}); diff --git a/__tests__/src/extend/pluginPreprocessing.test.js b/__tests__/src/extend/pluginPreprocessing.test.js index d695c53c98d613841e2cec87fe46782c456ec079..41177a1516f8b706597adf61c06d6ac8c3d0b9b1 100644 --- a/__tests__/src/extend/pluginPreprocessing.test.js +++ b/__tests__/src/extend/pluginPreprocessing.test.js @@ -3,6 +3,7 @@ import { connectPluginsToStore, addPluginReducersToStore, createTargetToPluginMapping, + addPluginsToCompanionWindowsRegistry, } from '../../../src/extend/pluginPreprocessing'; @@ -108,6 +109,26 @@ describe('connectPluginsToStore', () => { }); }); +describe('addPluginsToCompanionWindowsRegistry', () => { + it('adds plugin references to the companion window registry', () => { + /** */ + const ComponentA = props => null; + + const plugins = [ + { + companionWindowKey: 'xyz', + component: ComponentA, + mode: 'add', + target: 'CompanionWindowFactory', + }, + ]; + + const result = addPluginsToCompanionWindowsRegistry(plugins); + + expect(result.xyz).toBe(ComponentA); + }); +}); + describe('addPluginReducersToStore', () => { const store = { replaceReducer: jest.fn() }; const createRootReducer = jest.fn(pluginReducers => pluginReducers); diff --git a/src/components/CompanionWindowFactory.js b/src/components/CompanionWindowFactory.js index eecfcaaf41be4d9845c6d1cdc29e87389d378953..82516259ca08757a95e38e6e9beff1c11de103fd 100644 --- a/src/components/CompanionWindowFactory.js +++ b/src/components/CompanionWindowFactory.js @@ -1,13 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import ThumbnailNavigation from '../containers/ThumbnailNavigation'; -import WindowSideBarAnnotationsPanel from '../containers/WindowSideBarAnnotationsPanel'; -import WindowSideBarInfoPanel from '../containers/WindowSideBarInfoPanel'; -import WindowSideBarCanvasPanel from '../containers/WindowSideBarCanvasPanel'; -import AttributionPanel from '../containers/AttributionPanel'; -import SearchPanel from '../containers/SearchPanel'; -import LayersPanel from '../containers/LayersPanel'; -import CustomPanel from '../containers/CustomPanel'; +import CompanionWindowRegistry from '../lib/CompanionWindowRegistry'; /** * Render a companion window using the appropriate component for the content @@ -16,27 +9,11 @@ export class CompanionWindowFactory extends Component { /** */ render() { const { content, windowId, id } = this.props; + const type = CompanionWindowRegistry[content]; - switch (content) { - case 'info': - return (<WindowSideBarInfoPanel id={id} windowId={windowId} />); - case 'canvas': - return (<WindowSideBarCanvasPanel id={id} windowId={windowId} />); - case 'annotations': - return <WindowSideBarAnnotationsPanel id={id} windowId={windowId} />; - case 'thumbnailNavigation': - return <ThumbnailNavigation id={id} windowId={windowId} />; - case 'attribution': - return <AttributionPanel id={id} windowId={windowId} />; - case 'search': - return <SearchPanel id={id} windowId={windowId} />; - case 'layers': - return <LayersPanel id={id} windowId={windowId} />; - case 'custom': - return <CustomPanel id={id} windowId={windowId} />; - default: - return (<></>); - } + if (!type) return <></>; + + return React.createElement(type, { id, windowId }); } } diff --git a/src/components/WindowSideBarButtons.js b/src/components/WindowSideBarButtons.js index fe992e3475f55bcc10156a515589602f136d1f8d..279461019eb0216599dcf169e2258be3eeacc511 100644 --- a/src/components/WindowSideBarButtons.js +++ b/src/components/WindowSideBarButtons.js @@ -10,6 +10,7 @@ import AnnotationIcon from '@material-ui/icons/CommentSharp'; import AttributionIcon from '@material-ui/icons/CopyrightSharp'; import LayersIcon from '@material-ui/icons/LayersSharp'; import SearchIcon from '@material-ui/icons/SearchSharp'; +import { PluginHook } from './PluginHook'; import CanvasIndexIcon from './icons/CanvasIndexIcon'; import { keys, chars } from '../lib/KeyHelper'; /** @@ -63,6 +64,7 @@ export class WindowSideBarButtons extends Component { */ handleChange(event, value) { const { addCompanionWindow } = this.props; + const tab = event.target; this.selectTab(tab); addCompanionWindow(value); @@ -138,6 +140,7 @@ export class WindowSideBarButtons extends Component { hasSearchResults, hasSearchService, panels, + PluginComponents, sideBarPanel, t, } = this.props; @@ -217,6 +220,7 @@ export class WindowSideBarButtons extends Component { )} /> )} + <PluginHook TabButton={TabButton} PluginComponents={PluginComponents} /> </Tabs> ); } @@ -231,6 +235,7 @@ WindowSideBarButtons.propTypes = { hasSearchResults: PropTypes.bool, hasSearchService: PropTypes.bool, panels: PropTypes.arrayOf(PropTypes.bool), + PluginComponents: PropTypes.array, // eslint-disable-line react/forbid-prop-types sideBarPanel: PropTypes.string, t: PropTypes.func, }; @@ -243,6 +248,7 @@ WindowSideBarButtons.defaultProps = { hasSearchResults: false, hasSearchService: false, panels: [], + PluginComponents: null, sideBarPanel: 'closed', t: key => key, }; diff --git a/src/extend/PluginProvider.js b/src/extend/PluginProvider.js index e3c62358d03606d9fd452d9f06813834eabea943..6d4f327b61cb6d25f968067e0939396c5587433b 100644 --- a/src/extend/PluginProvider.js +++ b/src/extend/PluginProvider.js @@ -7,6 +7,7 @@ import { addPluginReducersToStore, connectPluginsToStore, createTargetToPluginMapping, + addPluginsToCompanionWindowsRegistry, } from './pluginPreprocessing'; /** */ @@ -19,6 +20,7 @@ export default function PluginProvider(props) { const validPlugins = filterValidPlugins(plugins); const connectedPlugins = connectPluginsToStore(validPlugins); createRootReducer && addPluginReducersToStore(store, createRootReducer, validPlugins); + addPluginsToCompanionWindowsRegistry(connectedPlugins); setPluginMap(createTargetToPluginMapping(connectedPlugins)); }, []); diff --git a/src/extend/pluginPreprocessing.js b/src/extend/pluginPreprocessing.js index 77ac16ad4bec31f98ceb5d4419328342055d029a..e8c16801bb89856903690ae0dcb74f550e3715d0 100644 --- a/src/extend/pluginPreprocessing.js +++ b/src/extend/pluginPreprocessing.js @@ -1,6 +1,7 @@ import update from 'lodash/update'; import { connect } from 'react-redux'; import { validatePlugin } from './pluginValidation'; +import CompanionWindowRegistry from '../lib/CompanionWindowRegistry'; /** * Returns a mapping from targets to plugins and modes @@ -42,6 +43,15 @@ export function addPluginReducersToStore(store, createRootReducer, plugins) { store.replaceReducer(createRootReducer(pluginReducers)); } +/** */ +export function addPluginsToCompanionWindowsRegistry(plugins) { + plugins.filter(p => p.companionWindowKey).forEach((plugin) => { + CompanionWindowRegistry[plugin.companionWindowKey] = plugin.component; + }); + + return CompanionWindowRegistry; +} + /** */ function splitPluginsByValidation(plugins) { const splittedPlugins = { invalidPlugins: [], validPlugins: [] }; diff --git a/src/lib/CompanionWindowRegistry.js b/src/lib/CompanionWindowRegistry.js new file mode 100644 index 0000000000000000000000000000000000000000..5197a0af1c54aa99279320e960ccd7e5abeb1381 --- /dev/null +++ b/src/lib/CompanionWindowRegistry.js @@ -0,0 +1,21 @@ +import ThumbnailNavigation from '../containers/ThumbnailNavigation'; +import WindowSideBarAnnotationsPanel from '../containers/WindowSideBarAnnotationsPanel'; +import WindowSideBarInfoPanel from '../containers/WindowSideBarInfoPanel'; +import WindowSideBarCanvasPanel from '../containers/WindowSideBarCanvasPanel'; +import AttributionPanel from '../containers/AttributionPanel'; +import SearchPanel from '../containers/SearchPanel'; +import LayersPanel from '../containers/LayersPanel'; +import CustomPanel from '../containers/CustomPanel'; + +const map = { + annotations: WindowSideBarAnnotationsPanel, + attribution: AttributionPanel, + canvas: WindowSideBarCanvasPanel, + custom: CustomPanel, + info: WindowSideBarInfoPanel, + layers: LayersPanel, + search: SearchPanel, + thumbnailNavigation: ThumbnailNavigation, +}; + +export default map;