From 0e56c3863e25ee2a6e4dda73135917d8b31524df Mon Sep 17 00:00:00 2001
From: Chris Beer <cabeer@stanford.edu>
Date: Fri, 15 Mar 2019 09:54:11 -0700
Subject: [PATCH] Use ResizeObserver to store the width + height of the elastic
 viewport in redux state

---
 __tests__/src/actions/workspace.test.js       | 17 ++++++++++++++
 .../src/components/WorkspaceElastic.test.js   | 23 +++++++++++++++++++
 __tests__/src/reducers/workspace.test.js      |  4 ++++
 package.json                                  |  1 +
 src/components/WorkspaceElastic.js            |  7 ++++++
 src/containers/WorkspaceElastic.js            |  6 ++++-
 src/state/actions/workspace.js                | 23 ++++++++++++++++---
 src/state/reducers/workspace.js               |  8 ++++++-
 8 files changed, 84 insertions(+), 5 deletions(-)

diff --git a/__tests__/src/actions/workspace.test.js b/__tests__/src/actions/workspace.test.js
index 5f430096a..d20bc6144 100644
--- a/__tests__/src/actions/workspace.test.js
+++ b/__tests__/src/actions/workspace.test.js
@@ -49,6 +49,23 @@ describe('workspace actions', () => {
       expect(actions.setWorkspaceAddVisibility(true)).toEqual(expectedAction);
     });
   });
+  describe('setWorkspaceViewportDimensions', () => {
+    it('should set the workspace add visibility', () => {
+      const expectedAction = {
+        type: ActionTypes.SET_WORKSPACE_VIEWPORT_POSITION,
+        payload: {
+          position: {
+            width: 20,
+            height: 25,
+          },
+        },
+      };
+      expect(actions.setWorkspaceViewportDimensions({
+        width: 20,
+        height: 25,
+      })).toEqual(expectedAction);
+    });
+  });
   describe('setWorkspaceViewportPosition', () => {
     it('should set the workspace add visibility', () => {
       const expectedAction = {
diff --git a/__tests__/src/components/WorkspaceElastic.test.js b/__tests__/src/components/WorkspaceElastic.test.js
index cba74aa1b..fde419fca 100644
--- a/__tests__/src/components/WorkspaceElastic.test.js
+++ b/__tests__/src/components/WorkspaceElastic.test.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import { Rnd } from 'react-rnd';
+import ResizeObserver from 'react-resize-observer';
 import WorkspaceElastic from '../../../src/components/WorkspaceElastic';
 
 /** create wrapper */
@@ -17,6 +18,7 @@ function createWrapper(props) {
           y: 20,
         },
       }}
+      setWorkspaceViewportDimensions={() => {}}
       setWorkspaceViewportPosition={() => {}}
       setWindowSize={() => {}}
       updateWindowPosition={() => {}}
@@ -143,5 +145,26 @@ describe('WorkspaceElastic', () => {
         y: -2700,
       });
     });
+
+    it('when workspace itself is resized', () => {
+      const mockResize = jest.fn();
+      wrapper = createWrapper({
+        windows,
+        setWorkspaceViewportDimensions: mockResize,
+      });
+
+      wrapper
+        .find(ResizeObserver)
+        .at(0)
+        .props()
+        .onResize({
+          width: 500,
+          height: 500,
+        });
+      expect(mockResize).toHaveBeenCalledWith({
+        width: 500,
+        height: 500,
+      });
+    });
   });
 });
diff --git a/__tests__/src/reducers/workspace.test.js b/__tests__/src/reducers/workspace.test.js
index e4d40d594..14ffd01f5 100644
--- a/__tests__/src/reducers/workspace.test.js
+++ b/__tests__/src/reducers/workspace.test.js
@@ -60,12 +60,16 @@ describe('workspace reducer', () => {
         position: {
           x: 50,
           y: 50,
+          width: 50,
+          height: 50,
         },
       },
     })).toEqual({
       viewportPosition: {
         x: 50,
         y: 50,
+        width: 50,
+        height: 50,
       },
     });
   });
diff --git a/package.json b/package.json
index 9d8d741a6..aaee48b36 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,7 @@
     "react-mosaic-component": "^2.1.0",
     "react-placeholder": "^3.0.1",
     "react-redux": "^6.0.0",
+    "react-resize-observer": "^1.1.1",
     "react-rnd": "^9.1.1",
     "react-virtualized": "^9.21.0",
     "redux": "4.0.1",
diff --git a/src/components/WorkspaceElastic.js b/src/components/WorkspaceElastic.js
index a15153218..412d848d7 100644
--- a/src/components/WorkspaceElastic.js
+++ b/src/components/WorkspaceElastic.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Rnd } from 'react-rnd';
+import ResizeObserver from 'react-resize-observer';
 import Window from '../containers/Window';
 import ns from '../config/css-ns';
 
@@ -16,6 +17,7 @@ class WorkspaceElastic extends React.Component {
     const {
       workspace,
       windows,
+      setWorkspaceViewportDimensions,
       setWorkspaceViewportPosition,
       updateWindowPosition,
       setWindowSize,
@@ -27,6 +29,10 @@ class WorkspaceElastic extends React.Component {
 
     return (
       <div style={{ position: 'relative', width: '100%', height: '100%' }}>
+        <ResizeObserver
+          onResize={(rect) => { setWorkspaceViewportDimensions(rect); }}
+        />
+
         <Rnd
           default={{
             width: workspace.width,
@@ -88,6 +94,7 @@ class WorkspaceElastic extends React.Component {
 
 WorkspaceElastic.propTypes = {
   setWorkspaceViewportPosition: PropTypes.func.isRequired,
+  setWorkspaceViewportDimensions: 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
   updateWindowPosition: PropTypes.func.isRequired,
diff --git a/src/containers/WorkspaceElastic.js b/src/containers/WorkspaceElastic.js
index 8c0b8d714..78e66ad73 100644
--- a/src/containers/WorkspaceElastic.js
+++ b/src/containers/WorkspaceElastic.js
@@ -15,13 +15,17 @@ const mapStateToProps = state => (
   }
 );
 
-
 /**
  * mapDispatchToProps - used to hook up connect to action creators
  * @memberof Workspace
  * @private
  */
 const mapDispatchToProps = (dispatch, props) => ({
+  setWorkspaceViewportDimensions: (position) => {
+    dispatch(
+      actions.setWorkspaceViewportDimensions(position),
+    );
+  },
   setWorkspaceViewportPosition: (position) => {
     dispatch(
       actions.setWorkspaceViewportPosition(position),
diff --git a/src/state/actions/workspace.js b/src/state/actions/workspace.js
index 6da0e4898..a75bfe5b5 100644
--- a/src/state/actions/workspace.js
+++ b/src/state/actions/workspace.js
@@ -45,18 +45,35 @@ export function setWorkspaceAddVisibility(isWorkspaceAddVisible) {
  * @param  {Object} position
  * @memberof ActionCreators
  */
-export function setWorkspaceViewportPosition(position) {
+export function setWorkspaceViewportPosition({ x, y }) {
   return {
     type: ActionTypes.SET_WORKSPACE_VIEWPORT_POSITION,
     payload: {
       position: {
-        x: position.x,
-        y: position.y,
+        x,
+        y,
       },
     },
   };
 }
 
+/**
+ * setWorkspaceViewportDimensions - action creator
+ *
+ * @param  {Object} position
+ * @memberof ActionCreators
+ */
+export function setWorkspaceViewportDimensions({ width, height }) {
+  return {
+    type: ActionTypes.SET_WORKSPACE_VIEWPORT_POSITION,
+    payload: {
+      position: {
+        width,
+        height,
+      },
+    },
+  };
+}
 /**
  * toggleWorkspaceExposeMode - action creator
  *
diff --git a/src/state/reducers/workspace.js b/src/state/reducers/workspace.js
index d9606abd8..ae1939f1a 100644
--- a/src/state/reducers/workspace.js
+++ b/src/state/reducers/workspace.js
@@ -34,7 +34,13 @@ 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,
+        viewportPosition: {
+          ...state.viewportPosition,
+          ...action.payload.position,
+        },
+      };
     case ActionTypes.TOGGLE_WORKSPACE_EXPOSE_MODE:
       return { ...state, exposeModeOn: !state.exposeModeOn };
     default:
-- 
GitLab