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 @@
<script type="text/javascript">
var miradorInstance = Mirador.viewer({
id: 'mirador',
workspace: {
type: 'elastic'
},
windows: [{
loadedManifest: 'https://iiif.harvardartmuseums.org/manifests/object/299843',
canvasIndex: 2,
......
......@@ -46,13 +46,11 @@ describe('workspace reducer', () => {
expect(workspaceReducer([], {
type: ActionTypes.SET_WORKSPACE_VIEWPORT_POSITION,
payload: {
position: {
x: 50,
y: 50,
},
},
})).toEqual({
viewportPosition: {
viewport: {
x: 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';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Typography from '@material-ui/core/Typography';
import ResizeObserver from 'react-resize-observer';
import Window from '../containers/Window';
import WorkspaceMosaic from '../containers/WorkspaceMosaic';
import WorkspaceElastic from '../containers/WorkspaceElastic';
......@@ -61,7 +62,7 @@ export class Workspace extends React.Component {
* render
*/
render() {
const { isWorkspaceControlPanelVisible, t } = this.props;
const { isWorkspaceControlPanelVisible, setWorkspaceViewportDimensions, t } = this.props;
return (
<div
......@@ -74,6 +75,12 @@ export class Workspace extends React.Component {
>
<Typography variant="srOnly" component="h1">{t('miradorViewer')}</Typography>
{this.workspaceByType()}
<ResizeObserver
onResize={(rect) => {
setWorkspaceViewportDimensions(rect.width, rect.height);
}}
/>
</div>
);
}
......@@ -81,6 +88,7 @@ export class Workspace extends React.Component {
Workspace.propTypes = {
isWorkspaceControlPanelVisible: PropTypes.bool.isRequired,
setWorkspaceViewportDimensions: PropTypes.func.isRequired,
windows: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
workspaceType: PropTypes.string.isRequired, // eslint-disable-line react/forbid-prop-types
t: PropTypes.func.isRequired,
......
......@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Rnd } from 'react-rnd';
import Window from '../containers/Window';
import ElasticMinimap from '../containers/ElasticMinimap';
import ns from '../config/css-ns';
/**
......@@ -21,12 +22,13 @@ class WorkspaceElastic extends React.Component {
setWindowSize,
} = this.props;
return (
<>
<Rnd
default={{
width: 5000,
height: 5000,
}}
position={{ x: workspace.viewportPosition.x, y: workspace.viewportPosition.y }}
position={{ x: workspace.viewport.x, y: workspace.viewport.y }}
enableResizing={{
top: false,
right: false,
......@@ -37,8 +39,8 @@ class WorkspaceElastic extends React.Component {
bottomLeft: false,
topLeft: false,
}}
onDragStop={(e, d) => {
setWorkspaceViewportPosition({ x: d.x, y: d.y });
onDrag={(e, d) => {
setWorkspaceViewportPosition(d.x, d.y);
}}
cancel={`.${ns('window')}`}
className={ns('workspace')}
......@@ -50,13 +52,13 @@ class WorkspaceElastic extends React.Component {
size={{ width: window.width, height: window.height }}
position={{ x: window.x, y: window.y }}
bounds="parent"
onDragStop={(e, d) => {
onDrag={(e, d) => {
updateWindowPosition(window.id, { x: d.x, y: d.y });
}}
onResize={(e, direction, ref, delta, position) => {
setWindowSize(window.id, {
width: ref.style.width,
height: ref.style.height,
width: parseInt(ref.style.width, 10),
height: parseInt(ref.style.height, 10),
...position,
});
}}
......@@ -69,6 +71,8 @@ class WorkspaceElastic extends React.Component {
))
}
</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 { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import * as actions from '../state/actions';
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
* @memberof Workspace
......@@ -18,7 +33,7 @@ const mapStateToProps = state => (
const enhance = compose(
withTranslation(),
connect(mapStateToProps),
connect(mapStateToProps, mapDispatchToProps),
// further HOC go here
);
......
......@@ -22,9 +22,9 @@ const mapStateToProps = state => (
* @private
*/
const mapDispatchToProps = (dispatch, props) => ({
setWorkspaceViewportPosition: (position) => {
setWorkspaceViewportPosition: (x, y) => {
dispatch(
actions.setWorkspaceViewportPosition(position),
actions.setWorkspaceViewportPosition(x, y),
);
},
toggleWorkspaceExposeMode: size => dispatch(
......
......@@ -10,6 +10,7 @@ const ActionTypes = {
FOCUS_WINDOW: 'FOCUS_WINDOW',
SET_WORKSPACE_FULLSCREEN: 'SET_WORKSPACE_FULLSCREEN',
SET_WORKSPACE_VIEWPORT_DIMENSIONS: 'SET_WORKSPACE_VIEWPORT_DIMENSIONS',
SET_WORKSPACE_VIEWPORT_POSITION: 'SET_WORKSPACE_VIEWPORT_POSITION',
TOGGLE_WORKSPACE_EXPOSE_MODE: 'TOGGLE_WORKSPACE_EXPOSE_MODE',
ADD_MANIFEST: 'ADD_MANIFEST',
......
......@@ -45,14 +45,29 @@ export function setWorkspaceAddVisibility(isWorkspaceAddVisible) {
* @param {Object} position
* @memberof ActionCreators
*/
export function setWorkspaceViewportPosition(position) {
export function setWorkspaceViewportPosition(x, y) {
console.log('x:', x, 'y', y);
return {
type: ActionTypes.SET_WORKSPACE_VIEWPORT_POSITION,
payload: {
position: {
x: position.x,
y: position.y,
x,
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';
*/
export const workspaceReducer = (
state = { // we'll need to abstract this more, methinks.
viewportPosition: {
viewport: {
x: -2500,
y: -2500,
width: 800,
height: 600,
},
exposeModeOn: false,
},
......@@ -25,7 +27,23 @@ export const workspaceReducer = (
case ActionTypes.SET_WORKSPACE_ADD_VISIBILITY:
return { ...state, isWorkspaceAddVisible: action.isWorkspaceAddVisible };
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:
return { ...state, exposeModeOn: !state.exposeModeOn };
default:
......
......@@ -272,3 +272,45 @@ export function getLanguagesFromConfigWithCurrent(state) {
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 @@
}
&-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 {
......@@ -56,20 +56,57 @@
&-workspace-add {
height: 100%;
overflow: auto;
padding-left: 6px;
padding-right: 6px;
padding-top: 92px;
margin-left: 6px;
margin-right: 6px;
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) {
&-workspace-with-control-panel {
padding-left: 100px;
padding-top: 0;
margin-left: 100px;
margin-top: 0;
}
&-workspace-add {
padding-left: 100px;
padding-top: 18px;
margin-left: 100px;
margin-top: 18px;
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment