Skip to content
Snippets Groups Projects
Commit 9e2405ff authored by Andrew Winget (Standard)'s avatar Andrew Winget (Standard)
Browse files

adds overlapping, panning windowing system using react-Rnd

parent a5909ebe
Branches
No related tags found
No related merge requests found
......@@ -30,7 +30,7 @@ class App extends Component {
});
return (
<div className={classNames(classes.background, ns('app'))}>
<div className={classNames(classes.background, ns('viewer'))}>
<I18nextProvider i18n={i18n}>
<MuiThemeProvider theme={createMuiTheme(theme)}>
<Fullscreen
......
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Rnd } from 'react-rnd';
import ns from '../config/css-ns';
import WindowTopBar from '../containers/WindowTopBar';
import WindowMiddleContent from '../containers/WindowMiddleContent';
......@@ -11,10 +12,15 @@ import ThumbnailNavigation from '../containers/ThumbnailNavigation';
*/
class Window extends Component {
/**
* Renders things
* Represents a Window in the mirador workspace
* @param {object} window
*/
render() {
const { manifest, window } = this.props;
baseWindow() {
const {
manifest,
window,
} = this.props;
return (
<div id={window.id} className={ns('window')}>
<WindowTopBar
......@@ -35,11 +41,56 @@ class Window extends Component {
</div>
);
}
/**
* Represents a Window in the mirador workspace
* @param {object} window
*/
draggableWorkspaceWindow() {
const {
window,
updateWindowPosition,
setWindowSize,
} = this.props;
return (
<Rnd
size={{ width: window.width, height: window.height }}
position={{ x: window.x, y: window.y }}
bounds="parent"
onDragStop={(e, d) => {
updateWindowPosition({ x: d.x, y: d.y });
}}
onResize={(e, direction, ref, delta, position) => {
setWindowSize({
width: ref.style.width,
height: ref.style.height,
...position,
});
}}
dragHandleClassName={ns('window-top-bar')}
>
{ this.baseWindow() }
</Rnd>
);
}
/**
* Renders things
*/
render() {
const { workspaceType } = this.props;
return workspaceType === 'freeform'
? this.draggableWorkspaceWindow() : this.baseWindow();
}
}
Window.propTypes = {
window: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
manifest: PropTypes.object, // eslint-disable-line react/forbid-prop-types
updateWindowPosition: PropTypes.func.isRequired,
setWindowSize: PropTypes.func.isRequired,
workspaceType: PropTypes.string.isRequired,
};
Window.defaultProps = {
......
......@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import Window from '../containers/Window';
import WorkspaceMosaic from '../containers/WorkspaceMosaic';
import WorkspaceFreeform from '../containers/WorkspaceFreeform';
import ns from '../config/css-ns';
/**
......@@ -17,6 +18,8 @@ class Workspace extends React.Component {
workspaceByType() {
const { workspaceType, windows } = this.props;
switch (workspaceType) {
case 'freeform':
return <WorkspaceFreeform />;
case 'mosaic':
return <WorkspaceMosaic windows={windows} />;
default:
......@@ -39,7 +42,7 @@ class Workspace extends React.Component {
<div
className={
classNames(
ns('workspace'),
ns('workspace-viewport'),
(isWorkspaceControlPanelVisible && ns('workspace-with-control-panel')),
)
}
......
import React from 'react';
import PropTypes from 'prop-types';
import { Rnd } from 'react-rnd';
import Window from '../containers/Window';
import ns from '../config/css-ns';
/**
* Represents a work area that contains any number of windows
* @memberof Workspace
* @private
*/
class WorkspaceFreeform extends React.Component {
/**
*/
render() {
const {
workspace,
windows,
setWorkspaceViewportPosition,
} = this.props;
return (
<Rnd
default={{
width: 5000,
height: 5000,
}}
position={{ x: workspace.viewportPosition.x, y: workspace.viewportPosition.y }}
enableResizing={{
top: false,
right: false,
bottom: false,
left: false,
topRight: false,
bottomRight: false,
bottomLeft: false,
topLeft: false,
}}
onDragStop={(e, d) => {
setWorkspaceViewportPosition({ x: d.x, y: d.y });
}}
cancel={`.${ns('window')}`}
className={ns('workspace')}
>
{
Object.values(windows).map(window => (
<Window
key={window.id}
window={window}
/>
))
}
</Rnd>
);
}
}
WorkspaceFreeform.propTypes = {
setWorkspaceViewportPosition: PropTypes.func.isRequired,
windows: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
workspace: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};
export default WorkspaceFreeform;
......@@ -18,9 +18,9 @@ export default {
height: 150,
},
workspace: {
type: 'mosaic',
type: 'freeform',
},
workspaceControlPanel: {
enabled: true,
}
},
};
import { compose } from 'redux';
import { connect } from 'react-redux';
import * as actions from '../state/actions';
import Window from '../components/Window';
/**
* mapDispatchToProps - used to hook up connect to action creators
* @memberof ManifestListItem
* @private
*/
const mapDispatchToProps = (dispatch, props) => ({
updateWindowPosition: (position) => {
dispatch(
actions.updateWindowPosition(props.window.id, position),
);
},
setWindowSize: size => dispatch(
actions.setWindowSize(props.window.id, size),
),
});
/**
* mapStateToProps - used to hook up connect to action creators
* @memberof Window
* @private
*/
const mapStateToProps = ({ manifests, windows }, props) => ({
const mapStateToProps = ({ config, manifests, windows }, props) => ({
workspaceType: config.workspace.type,
manifest: manifests[props.window.manifestId],
window: windows[props.window.id],
});
const enhance = compose(
connect(mapStateToProps),
connect(mapStateToProps, mapDispatchToProps),
// further HOC go here
);
......
import { compose } from 'redux';
import { connect } from 'react-redux';
import * as actions from '../state/actions';
import WorkspaceFreeform from '../components/WorkspaceFreeform';
/**
* mapStateToProps - to hook up connect
* @memberof Workspace
* @private
*/
const mapStateToProps = state => (
{
workspace: state.workspace,
config: state.config,
windows: state.windows,
}
);
/**
* mapDispatchToProps - used to hook up connect to action creators
* @memberof Workspace
* @private
*/
const mapDispatchToProps = (dispatch, props) => ({
setWorkspaceViewportPosition: (position) => {
dispatch(
actions.setWorkspaceViewportPosition(position),
);
},
toggleWorkspaceExposeMode: size => dispatch(
actions.toggleWorkspaceExposeMode(),
),
});
const enhance = compose(
connect(mapStateToProps, mapDispatchToProps),
// further HOC go here
);
export default enhance(WorkspaceFreeform);
const ActionTypes = {
FOCUS_WINDOW: 'FOCUS_WINDOW',
SET_WORKSPACE_FULLSCREEN: 'SET_WORKSPACE_FULLSCREEN',
SET_WORKSPACE_VIEWPORT_POSITION: 'SET_WORKSPACE_VIEWPORT_POSITION',
TOGGLE_WORKSPACE_EXPOSE_MODE: 'TOGGLE_WORKSPACE_EXPOSE_MODE',
ADD_MANIFEST: 'ADD_MANIFEST',
ADD_WINDOW: 'ADD_WINDOW',
NEXT_CANVAS: 'NEXT_CANVAS',
PREVIOUS_CANVAS: 'PREVIOUS_CANVAS',
SET_CANVAS: 'SET_CANVAS',
UPDATE_WINDOW_POSITION: 'UPDATE_WINDOW_POSITION',
SET_WINDOW_SIZE: 'SET_WINDOW_SIZE',
REMOVE_WINDOW: 'REMOVE_WINDOW',
PICK_WINDOWING_SYSTEM: 'PICK_WINDOWING_SYSTEM',
REQUEST_MANIFEST: 'REQUEST_MANIFEST',
......
......@@ -25,7 +25,10 @@ export function addWindow(options) {
manifestId: null,
rangeId: null,
thumbnailNavigationPosition: 'bottom', // bottom by default in settings.js
xywh: [0, 0, 400, 400],
width: 400,
height: 400,
x: 2700,
y: 2700,
rotation: null,
view: 'single',
};
......@@ -120,3 +123,37 @@ export function setWindowThumbnailPosition(windowId, position) {
export function setWindowViewType(windowId, viewType) {
return { type: ActionTypes.SET_WINDOW_VIEW_TYPE, windowId, viewType };
}
/**
* updateWindowPosition - action creator
*
* @param {String} windowId
* @param {Array} position
* @memberof ActionCreators
*/
export function updateWindowPosition(windowId, position) {
return {
type: ActionTypes.UPDATE_WINDOW_POSITION,
payload: {
windowId,
position,
},
};
}
/**
* setWindowSize - action creator
*
* @param {String} windowId
* @param {Object} size
* @memberof ActionCreators
*/
export function setWindowSize(windowId, size) {
return {
type: ActionTypes.SET_WINDOW_SIZE,
payload: {
windowId,
size,
},
};
}
import ActionTypes from './action-types';
/* eslint-disable import/prefer-default-export */
/**
* setWorkspaceFullscreen - action creator
*
......@@ -39,3 +38,33 @@ export function updateWorkspaceMosaicLayout(layout) {
export function setWorkspaceAddVisibility(isWorkspaceAddVisible) {
return { type: ActionTypes.SET_WORKSPACE_ADD_VISIBILITY, isWorkspaceAddVisible };
}
/**
* setWorkspaceViewportPosition - action creator
*
* @param {Object} position
* @memberof ActionCreators
*/
export function setWorkspaceViewportPosition(position) {
return {
type: ActionTypes.SET_WORKSPACE_VIEWPORT_POSITION,
payload: {
position: {
x: position.x,
y: position.y,
},
},
};
}
/**
* toggleWorkspaceExposeMode - action creator
*
* @param {Object} position
* @memberof ActionCreators
*/
export function toggleWorkspaceExposeMode() {
return {
type: ActionTypes.TOGGLE_WORKSPACE_EXPOSE_MODE,
};
}
......@@ -61,6 +61,26 @@ export const windowsReducer = (state = {}, action) => {
},
},
};
case ActionTypes.UPDATE_WINDOW_POSITION:
return {
...state,
[action.payload.windowId]: {
...state[action.payload.windowId],
x: action.payload.position.x,
y: action.payload.position.y,
},
};
case ActionTypes.SET_WINDOW_SIZE:
return {
...state,
[action.payload.windowId]: {
...state[action.payload.windowId],
width: action.payload.size.width,
height: action.payload.size.height,
x: action.payload.size.x,
y: action.payload.size.y,
},
};
case ActionTypes.NEXT_CANVAS:
return setCanvasIndex(state, action.windowId, currentIndex => currentIndex + 1);
case ActionTypes.PREVIOUS_CANVAS:
......
......@@ -3,7 +3,16 @@ import ActionTypes from '../actions/action-types';
/**
* workspaceReducer
*/
export const workspaceReducer = (state = {}, action) => {
export const workspaceReducer = (
state = { // we'll need to abstract this more, methinks.
viewportPosition: {
x: -2500,
y: -2500,
},
exposedModeOn: false,
},
action,
) => {
switch (action.type) {
case ActionTypes.FOCUS_WINDOW:
return { ...state, focusedWindowId: action.windowId };
......@@ -15,6 +24,10 @@ export const workspaceReducer = (state = {}, action) => {
return { ...state, layout: action.layout };
case ActionTypes.SET_WORKSPACE_ADD_VISIBILITY:
return { ...state, isWorkspaceAddVisible: action.isWorkspaceAddVisible };
case ActionTypes.SET_WORKSPACE_VIEWPORT_POSITION:
return { ...state, viewportPosition: action.payload.position };
case ActionTypes.TOGGLE_WORKSPACE_EXPOSE_MODE:
return { ...state, exposeModeOn: !state.exposedModeOn };
default:
return state;
}
......
@import 'variables';
.mirador {
&-app {
height: 100%;
position: relative;
width: 100%;
&-viewer {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
&-workspace {
&-workspace-viewport {
position: absolute;
top: 0;
right: 0;
bottom: 0;
box-sizing: border-box;
left: 0;
}
&-workspace {
box-sizing: border-box;
margin: 0;
overflow: scroll;
position: absolute;
right: 0;
top: 0;
}
&-workspace-with-control-panel {
......@@ -35,6 +41,8 @@
}
&-window {
width:100%;
height:100%;
display: flex;
flex-direction: column;
height: 100%;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment