Skip to content
Snippets Groups Projects
Commit 2b1fafbf authored by aeschylus's avatar aeschylus
Browse files

experiment

parent fd7227d4
No related tags found
No related merge requests found
...@@ -12,6 +12,9 @@ ...@@ -12,6 +12,9 @@
<script type="text/javascript"> <script type="text/javascript">
var miradorInstance = Mirador.viewer({ var miradorInstance = Mirador.viewer({
id: 'mirador', id: 'mirador',
workspace: {
type: 'elastic'
},
windows: [{ windows: [{
loadedManifest: 'https://iiif.harvardartmuseums.org/manifests/object/299843', loadedManifest: 'https://iiif.harvardartmuseums.org/manifests/object/299843',
canvasIndex: 2, canvasIndex: 2,
......
...@@ -46,13 +46,11 @@ describe('workspace reducer', () => { ...@@ -46,13 +46,11 @@ describe('workspace reducer', () => {
expect(workspaceReducer([], { expect(workspaceReducer([], {
type: ActionTypes.SET_WORKSPACE_VIEWPORT_POSITION, type: ActionTypes.SET_WORKSPACE_VIEWPORT_POSITION,
payload: { payload: {
position: {
x: 50, x: 50,
y: 50, y: 50,
}, },
},
})).toEqual({ })).toEqual({
viewportPosition: { viewport: {
x: 50, x: 50,
y: 50, y: 50,
}, },
......
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Rnd } from 'react-rnd';
import ns from '../config/css-ns';
import { getWorkspaceBoundingBox } from '../state/selectors';
const minimapContainer = {
width: 200,
height: 200,
innerPadding: 10,
};
// There is a minimum bounding box based on the viewport dimensions,
// and the bounding box is always square
/**
* ElasticMinimap
*/
const scaledViewport = (workspaceViewport, boundingBox) => {
const viewportAspectRatio = workspaceViewport.width / workspaceViewport.height;
const viewportScaleFactor = workspaceViewport.width / boundingBox.width;
const scaledViewportWidth = viewportScaleFactor * minimapContainer.width;
const scaledViewportHeight = scaledViewportWidth / viewportAspectRatio;
const scaledViewportX = (-workspaceViewport.x / boundingBox.width) * minimapContainer.width;
const scaledViewportY = (-workspaceViewport.y / boundingBox.height) * minimapContainer.height;
return {
x: scaledViewportX,
y: scaledViewportY,
width: scaledViewportWidth,
height: scaledViewportHeight,
};
};
/**
* ElasticMinimap
*/
const minimapToWorkspaceCoordinates = (x, y, boundingBox) => {
const newX = -x * boundingBox.width / minimapContainer.width;
const newY = -y * boundingBox.height / minimapContainer.height;
return {
x: newX,
y: newY,
};
};
/**
* ElasticMinimap
*/
const windowStyle = (window, boundingBox) => {
const windowAspectRatio = window.width / window.height;
const windowScaleFactor = window.width / boundingBox.width;
const scaledWindowWidth = windowScaleFactor * 100;
const scaledWindowX = (window.x / boundingBox.width) * 100;
const scaledWindowY = (window.y / boundingBox.height) * 100;
const scaledWindowHeight = scaledWindowWidth / windowAspectRatio;
return {
top: `${scaledWindowY}%`,
left: `${scaledWindowX}%`,
height: `${scaledWindowHeight}%`,
width: `${scaledWindowWidth}%`,
};
};
/**
* ElasticMinimap
*/
const boundingBoxStyle = (windows, boundingBox) => {
const windowBoundingBox = getWorkspaceBoundingBox(windows);
const windowBBAspectRatio = windowBoundingBox.width / windowBoundingBox.height;
const windowBBScaleFactor = windowBoundingBox.width / boundingBox.width;
const scaledWindowBBWidth = windowBBScaleFactor * 100;
const scaledWindowBBX = (windowBoundingBox.x / boundingBox.width) * 100;
const scaledWindowBBY = (windowBoundingBox.y / boundingBox.height) * 100;
const scaledWindowBBHeight = scaledWindowBBWidth / windowBBAspectRatio;
return {
top: `${scaledWindowBBY}%`,
left: `${scaledWindowBBX}%`,
height: `${scaledWindowBBHeight}%`,
width: `${scaledWindowBBWidth}%`,
};
};
/**
* ElasticMinimap
*/
export class ElasticMinimap extends Component {
/**
* render
* @return
*/
render() {
const boundingBox = {
width: 5000,
height: 5000,
};
const {
windows,
workspaceViewport,
setWorkspaceViewportPosition,
} = this.props;
const viewport = scaledViewport(
workspaceViewport,
boundingBox,
);
return (
<div className={ns('elastic-minimap')}>
{
Object.values(windows).map(window => (
<div
key={window.id}
className="minimap-window"
style={windowStyle(window, boundingBox)}
/>
))
}
<div
className="window-bounding-box"
style={boundingBoxStyle(windows, boundingBox)}
/>
<Rnd
position={{ x: viewport.x, y: viewport.y }}
size={{ width: viewport.width, height: viewport.height }}
enableResizing={{
top: false,
right: false,
bottom: false,
left: false,
topRight: false,
bottomRight: false,
bottomLeft: false,
topLeft: false,
}}
onDragStart={(e, d) => null}
onDrag={(e, d) => {
const newPosition = minimapToWorkspaceCoordinates(d.x, d.y, boundingBox);
setWorkspaceViewportPosition(newPosition.x, newPosition.y);
}}
className="minimap-viewport"
/>
</div>
);
}
}
ElasticMinimap.propTypes = {
windows: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
setWorkspaceViewportPosition: PropTypes.func.isRequired,
workspaceViewport: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};
...@@ -2,6 +2,7 @@ import React from 'react'; ...@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import ResizeObserver from 'react-resize-observer';
import Window from '../containers/Window'; import Window from '../containers/Window';
import WorkspaceMosaic from '../containers/WorkspaceMosaic'; import WorkspaceMosaic from '../containers/WorkspaceMosaic';
import WorkspaceElastic from '../containers/WorkspaceElastic'; import WorkspaceElastic from '../containers/WorkspaceElastic';
...@@ -61,7 +62,7 @@ export class Workspace extends React.Component { ...@@ -61,7 +62,7 @@ export class Workspace extends React.Component {
* render * render
*/ */
render() { render() {
const { isWorkspaceControlPanelVisible, t } = this.props; const { isWorkspaceControlPanelVisible, setWorkspaceViewportDimensions, t } = this.props;
return ( return (
<div <div
...@@ -74,6 +75,12 @@ export class Workspace extends React.Component { ...@@ -74,6 +75,12 @@ export class Workspace extends React.Component {
> >
<Typography variant="srOnly" component="h1">{t('miradorViewer')}</Typography> <Typography variant="srOnly" component="h1">{t('miradorViewer')}</Typography>
{this.workspaceByType()} {this.workspaceByType()}
<ResizeObserver
onResize={(rect) => {
setWorkspaceViewportDimensions(rect.width, rect.height);
}}
/>
</div> </div>
); );
} }
...@@ -81,6 +88,7 @@ export class Workspace extends React.Component { ...@@ -81,6 +88,7 @@ export class Workspace extends React.Component {
Workspace.propTypes = { Workspace.propTypes = {
isWorkspaceControlPanelVisible: PropTypes.bool.isRequired, isWorkspaceControlPanelVisible: PropTypes.bool.isRequired,
setWorkspaceViewportDimensions: PropTypes.func.isRequired,
windows: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types windows: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
workspaceType: PropTypes.string.isRequired, // eslint-disable-line react/forbid-prop-types workspaceType: PropTypes.string.isRequired, // eslint-disable-line react/forbid-prop-types
t: PropTypes.func.isRequired, t: PropTypes.func.isRequired,
......
...@@ -2,6 +2,7 @@ import React from 'react'; ...@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Rnd } from 'react-rnd'; import { Rnd } from 'react-rnd';
import Window from '../containers/Window'; import Window from '../containers/Window';
import ElasticMinimap from '../containers/ElasticMinimap';
import ns from '../config/css-ns'; import ns from '../config/css-ns';
/** /**
...@@ -21,12 +22,13 @@ class WorkspaceElastic extends React.Component { ...@@ -21,12 +22,13 @@ class WorkspaceElastic extends React.Component {
setWindowSize, setWindowSize,
} = this.props; } = this.props;
return ( return (
<>
<Rnd <Rnd
default={{ default={{
width: 5000, width: 5000,
height: 5000, height: 5000,
}} }}
position={{ x: workspace.viewportPosition.x, y: workspace.viewportPosition.y }} position={{ x: workspace.viewport.x, y: workspace.viewport.y }}
enableResizing={{ enableResizing={{
top: false, top: false,
right: false, right: false,
...@@ -37,8 +39,8 @@ class WorkspaceElastic extends React.Component { ...@@ -37,8 +39,8 @@ class WorkspaceElastic extends React.Component {
bottomLeft: false, bottomLeft: false,
topLeft: false, topLeft: false,
}} }}
onDragStop={(e, d) => { onDrag={(e, d) => {
setWorkspaceViewportPosition({ x: d.x, y: d.y }); setWorkspaceViewportPosition(d.x, d.y);
}} }}
cancel={`.${ns('window')}`} cancel={`.${ns('window')}`}
className={ns('workspace')} className={ns('workspace')}
...@@ -50,13 +52,13 @@ class WorkspaceElastic extends React.Component { ...@@ -50,13 +52,13 @@ class WorkspaceElastic extends React.Component {
size={{ width: window.width, height: window.height }} size={{ width: window.width, height: window.height }}
position={{ x: window.x, y: window.y }} position={{ x: window.x, y: window.y }}
bounds="parent" bounds="parent"
onDragStop={(e, d) => { onDrag={(e, d) => {
updateWindowPosition(window.id, { x: d.x, y: d.y }); updateWindowPosition(window.id, { x: d.x, y: d.y });
}} }}
onResize={(e, direction, ref, delta, position) => { onResize={(e, direction, ref, delta, position) => {
setWindowSize(window.id, { setWindowSize(window.id, {
width: ref.style.width, width: parseInt(ref.style.width, 10),
height: ref.style.height, height: parseInt(ref.style.height, 10),
...position, ...position,
}); });
}} }}
...@@ -69,6 +71,8 @@ class WorkspaceElastic extends React.Component { ...@@ -69,6 +71,8 @@ class WorkspaceElastic extends React.Component {
)) ))
} }
</Rnd> </Rnd>
<ElasticMinimap />
</>
); );
} }
} }
......
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import miradorWithPlugins from '../lib/miradorWithPlugins';
import * as actions from '../state/actions';
import { ElasticMinimap } from '../components/ElasticMinimap';
/**
* mapDispatchToProps - used to hook up connect to action creators
* @memberof ManifestListItem
* @private
*/
const mapDispatchToProps = (dispatch, props) => ({
setWorkspaceViewportPosition: (x, y) => {
dispatch(
actions.setWorkspaceViewportPosition(x, y),
);
},
});
/**
* mapStateToProps - to hook up connect
* @memberof WindowViewer
* @private
*/
const mapStateToProps = state => ({
// viewport: state.workspace.viewport,
// workspaceBoundingBox: state.workspace.viewport,
workspaceViewport: state.workspace.viewport,
windows: state.windows,
});
const enhance = compose(
withTranslation(),
connect(mapStateToProps, mapDispatchToProps),
miradorWithPlugins,
);
export default enhance(ElasticMinimap);
import { compose } from 'redux'; import { compose } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import * as actions from '../state/actions';
import { Workspace } from '../components/Workspace'; import { Workspace } from '../components/Workspace';
/**
* mapDispatchToProps - used to hook up connect to action creators
* @memberof ManifestListItem
* @private
*/
const mapDispatchToProps = (dispatch, props) => ({
setWorkspaceViewportDimensions: (width, height) => {
dispatch(
actions.setWorkspaceViewportDimensions(width, height),
);
},
});
/** /**
* mapStateToProps - to hook up connect * mapStateToProps - to hook up connect
* @memberof Workspace * @memberof Workspace
...@@ -18,7 +33,7 @@ const mapStateToProps = state => ( ...@@ -18,7 +33,7 @@ const mapStateToProps = state => (
const enhance = compose( const enhance = compose(
withTranslation(), withTranslation(),
connect(mapStateToProps), connect(mapStateToProps, mapDispatchToProps),
// further HOC go here // further HOC go here
); );
......
...@@ -22,9 +22,9 @@ const mapStateToProps = state => ( ...@@ -22,9 +22,9 @@ const mapStateToProps = state => (
* @private * @private
*/ */
const mapDispatchToProps = (dispatch, props) => ({ const mapDispatchToProps = (dispatch, props) => ({
setWorkspaceViewportPosition: (position) => { setWorkspaceViewportPosition: (x, y) => {
dispatch( dispatch(
actions.setWorkspaceViewportPosition(position), actions.setWorkspaceViewportPosition(x, y),
); );
}, },
toggleWorkspaceExposeMode: size => dispatch( toggleWorkspaceExposeMode: size => dispatch(
......
...@@ -10,6 +10,7 @@ const ActionTypes = { ...@@ -10,6 +10,7 @@ const ActionTypes = {
FOCUS_WINDOW: 'FOCUS_WINDOW', FOCUS_WINDOW: 'FOCUS_WINDOW',
SET_WORKSPACE_FULLSCREEN: 'SET_WORKSPACE_FULLSCREEN', SET_WORKSPACE_FULLSCREEN: 'SET_WORKSPACE_FULLSCREEN',
SET_WORKSPACE_VIEWPORT_DIMENSIONS: 'SET_WORKSPACE_VIEWPORT_DIMENSIONS',
SET_WORKSPACE_VIEWPORT_POSITION: 'SET_WORKSPACE_VIEWPORT_POSITION', SET_WORKSPACE_VIEWPORT_POSITION: 'SET_WORKSPACE_VIEWPORT_POSITION',
TOGGLE_WORKSPACE_EXPOSE_MODE: 'TOGGLE_WORKSPACE_EXPOSE_MODE', TOGGLE_WORKSPACE_EXPOSE_MODE: 'TOGGLE_WORKSPACE_EXPOSE_MODE',
ADD_MANIFEST: 'ADD_MANIFEST', ADD_MANIFEST: 'ADD_MANIFEST',
......
...@@ -45,14 +45,29 @@ export function setWorkspaceAddVisibility(isWorkspaceAddVisible) { ...@@ -45,14 +45,29 @@ export function setWorkspaceAddVisibility(isWorkspaceAddVisible) {
* @param {Object} position * @param {Object} position
* @memberof ActionCreators * @memberof ActionCreators
*/ */
export function setWorkspaceViewportPosition(position) { export function setWorkspaceViewportPosition(x, y) {
console.log('x:', x, 'y', y);
return { return {
type: ActionTypes.SET_WORKSPACE_VIEWPORT_POSITION, type: ActionTypes.SET_WORKSPACE_VIEWPORT_POSITION,
payload: { payload: {
position: { x,
x: position.x, y,
y: position.y,
}, },
};
}
/**
* setWorkspaceViewportPosition - action creator
*
* @param {Object} position
* @memberof ActionCreators
*/
export function setWorkspaceViewportDimensions(width, height) {
return {
type: ActionTypes.SET_WORKSPACE_VIEWPORT_DIMENSIONS,
payload: {
width,
height,
}, },
}; };
} }
......
...@@ -5,9 +5,11 @@ import ActionTypes from '../actions/action-types'; ...@@ -5,9 +5,11 @@ import ActionTypes from '../actions/action-types';
*/ */
export const workspaceReducer = ( export const workspaceReducer = (
state = { // we'll need to abstract this more, methinks. state = { // we'll need to abstract this more, methinks.
viewportPosition: { viewport: {
x: -2500, x: -2500,
y: -2500, y: -2500,
width: 800,
height: 600,
}, },
exposeModeOn: false, exposeModeOn: false,
}, },
...@@ -25,7 +27,23 @@ export const workspaceReducer = ( ...@@ -25,7 +27,23 @@ export const workspaceReducer = (
case ActionTypes.SET_WORKSPACE_ADD_VISIBILITY: case ActionTypes.SET_WORKSPACE_ADD_VISIBILITY:
return { ...state, isWorkspaceAddVisible: action.isWorkspaceAddVisible }; return { ...state, isWorkspaceAddVisible: action.isWorkspaceAddVisible };
case ActionTypes.SET_WORKSPACE_VIEWPORT_POSITION: case ActionTypes.SET_WORKSPACE_VIEWPORT_POSITION:
return { ...state, viewportPosition: action.payload.position }; return {
...state,
viewport: {
...state.viewport,
x: action.payload.x,
y: action.payload.y,
},
};
case ActionTypes.SET_WORKSPACE_VIEWPORT_DIMENSIONS:
return {
...state,
viewport: {
...state.viewport,
width: action.payload.width,
height: action.payload.height,
},
};
case ActionTypes.TOGGLE_WORKSPACE_EXPOSE_MODE: case ActionTypes.TOGGLE_WORKSPACE_EXPOSE_MODE:
return { ...state, exposeModeOn: !state.exposeModeOn }; return { ...state, exposeModeOn: !state.exposeModeOn };
default: default:
......
...@@ -272,3 +272,45 @@ export function getLanguagesFromConfigWithCurrent(state) { ...@@ -272,3 +272,45 @@ export function getLanguagesFromConfigWithCurrent(state) {
current: key === language, current: key === language,
})); }));
} }
/**
* Return the bounding box for all open windows in the elastic workspace
* in workspace coordinates
* @param {object} state
* @return {object}
*/
export function getWorkspaceBoundingBox(windows) {
const windowObjects = Object.values(windows);
const minX = Math.min(...windowObjects.map(win => win.x));
const minY = Math.min(...windowObjects.map(win => win.y));
const maxX = Math.max(...windowObjects.map((win) => {
console.log('win width: ', win.width);
console.log(win.x + win.width);
return (win.x + win.width);
}));
const maxY = Math.max(...windowObjects.map(win => (win.y + win.height)));
console.log('min X: ', minX);
console.log('min Y: ', minY);
console.log('max X: ', maxX);
console.log('max Y: ', maxY);
return {
x: minX,
y: minY,
width: (maxX - minX),
height: maxY - minY,
};
// Potential solution to overflow problems.
// const minX = Math.min(...windowObjects.map(
// win => Number.parseFloat(win.x).toPrecision(4),
// ));
// const minY = Math.min(...windowObjects.map(
// win => Number.parseFloat(win.y).toPrecision(4),
// ));
// const maxX = Math.max(...windowObjects.map(
// win => Number.parseFloat((win.x + win.width)).toPrecision(4),
// ));
// const maxY = Math.max(...windowObjects.map(
// win => Number.parseFloat((win.y + win.height)).toPrecision(4),
// ));
}
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
} }
&-workspace-with-control-panel { &-workspace-with-control-panel {
padding-top: 74px; // The height of the control panel margin-top: 74px; // The height of the control panel
} }
&-workspace-maximized-window { &-workspace-maximized-window {
...@@ -56,20 +56,57 @@ ...@@ -56,20 +56,57 @@
&-workspace-add { &-workspace-add {
height: 100%; height: 100%;
overflow: auto; overflow: auto;
padding-left: 6px; margin-left: 6px;
padding-right: 6px; margin-right: 6px;
padding-top: 92px; margin-top: 92px;
}
&-elastic-minimap {
opacity: 0.8;
position: absolute;
bottom: 0;
right: 0;
margin: 20px;
width: 200px;
height: 200px;
border: 2px solid lightgray;
background: white;
box-sizing: border-box;
.minimap-window {
position: absolute;
background: rgba(deepskyblue, .3);
border: 1px solid lightgray;
box-sizing: border-box;
}
.minimap-viewport {
position: absolute;
border: 1px solid orangered;
box-sizing: border-box;
transition: 0.2s box-shadow ease-out;
&:hover {
transition: 0.2s box-shadow ease-out;
box-shadow: 0 0 3px gray;
}
}
.window-bounding-box {
box-sizing: border-box;
position: absolute;
border: 1px dotted deeppink;
}
} }
@media (min-width: 600px) { @media (min-width: 600px) {
&-workspace-with-control-panel { &-workspace-with-control-panel {
padding-left: 100px; margin-left: 100px;
padding-top: 0; margin-top: 0;
} }
&-workspace-add { &-workspace-add {
padding-left: 100px; margin-left: 100px;
padding-top: 18px; margin-top: 18px;
} }
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment