UNPKG

mirador

Version:

An open-source, web-based 'multi-up' viewer that supports zoom-pan-rotate functionality, ability to display/compare simple images, and images with annotations.

164 lines (140 loc) 4.93 kB
import { useContext, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import { styled } from '@mui/material/styles'; import GlobalStyles from '@mui/material/GlobalStyles'; import { DndContext } from 'react-dnd'; import { Mosaic, MosaicWindow, getLeaves, createBalancedTreeFromLeaves, } from 'react-mosaic-component2'; import difference from 'lodash/difference'; import isEqual from 'lodash/isEqual'; import classNames from 'classnames'; import MosaicRenderPreview from '../containers/MosaicRenderPreview'; import Window from '../containers/Window'; import MosaicLayout from '../lib/MosaicLayout'; import globalReactMosaicStyles from '../styles/react-mosaic-component'; const StyledMosaic = styled(Mosaic)({ '& .mosaic-preview': { boxShadow: 'none', }, '& .mosaic-tile': { boxShadow: '0 1px 3px 0 rgba(0, 0, 0, .2), 0 1px 1px 0 rgba(0, 0, 0, .2), 0 2px 1px -1px rgba(0, 0, 0, .2)', }, '& .mosaic-window': { boxShadow: 'none', }, '& .mosaic-window-toolbar': { display: 'none !important', }, }); /** */ const RenderPreview = ({ windowId }) => ( <div className="mosaic-preview" aria-hidden> <MosaicRenderPreview windowId={windowId} /> </div> ); RenderPreview.propTypes = { windowId: PropTypes.string.isRequired, }; /** */ const ZeroStateView = () => (<div />); /** * Used to regenerate a new layout when windows are added or removed */ const determineWorkspaceLayout = (currentLayout, windowIds, currentWindowPaths = {}) => { const leaveKeys = getLeaves(currentLayout); const mosaicLayout = new MosaicLayout(currentLayout); // Add new windows to layout const addedWindows = difference(windowIds, leaveKeys); if (addedWindows.length > 0) mosaicLayout.addWindows(addedWindows); const removedWindows = difference(leaveKeys, windowIds); // if we have paths for the removed windows, we can gracefully remove them and // preserve the existing layout for the remaining windows if (removedWindows.every(e => currentWindowPaths[e])) { mosaicLayout.removeWindows(removedWindows, currentWindowPaths); } else { // Windows were removed (perhaps in a different Workspace). We don't have a // way to reconfigure.. so we have to random generate return createBalancedTreeFromLeaves(windowIds); } return mosaicLayout.layout; }; /** * Represents a work area that contains any number of windows * @memberof Workspace * @private */ export function WorkspaceMosaic({ layout = undefined, updateWorkspaceMosaicLayout, windowIds = [], workspaceId, }) { const toolbarControls = []; const additionalControls = []; const windowPaths = useRef({}); const dndContext = useContext(DndContext); useEffect(() => { const leaveKeys = getLeaves(layout); // Handle some trivial layout cases: // 1. No layout // 2. No windows // 3. Not enough windows to create a layout if (!layout || windowIds.length === 0 || leaveKeys.length < 2) { updateWorkspaceMosaicLayout(createBalancedTreeFromLeaves(windowIds)); return undefined; } // nothing was added or removed, and all the windows are accounted for in the layout if (windowIds.every(e => leaveKeys.includes(e)) && leaveKeys.every(e => windowIds.includes(e))) { return undefined; } const newLayout = determineWorkspaceLayout(layout, windowIds, windowPaths.current); if (!isEqual(newLayout, layout)) updateWorkspaceMosaicLayout(newLayout); return undefined; }, [layout, windowIds, windowPaths, updateWorkspaceMosaicLayout]); /** * Render a tile (Window) in the Mosaic. */ const tileRenderer = (id, path) => { if (!windowIds.includes(id)) return null; windowPaths.current[id] = path; return ( <MosaicWindow toolbarControls={toolbarControls} additionalControls={additionalControls} path={path} windowId={id} renderPreview={RenderPreview} > <Window key={`${id}-${workspaceId}`} windowId={id} /> </MosaicWindow> ); }; /** * Update the redux store when the Mosaic is changed. */ const mosaicChange = (newLayout) => { updateWorkspaceMosaicLayout(newLayout); }; return ( <> <GlobalStyles styles={{ ...globalReactMosaicStyles }} /> <StyledMosaic dragAndDropManager={dndContext.dragDropManager} renderTile={tileRenderer} initialValue={layout || createBalancedTreeFromLeaves(windowIds)} onChange={mosaicChange} className={classNames('mirador-mosaic')} zeroStateView={<ZeroStateView />} /> </> ); } WorkspaceMosaic.propTypes = { layout: PropTypes.oneOfType( [PropTypes.object, PropTypes.string], ), // eslint-disable-line react/forbid-prop-types updateWorkspaceMosaicLayout: PropTypes.func.isRequired, windowIds: PropTypes.arrayOf(PropTypes.string), workspaceId: PropTypes.string.isRequired, };