From 0158e1a70825b3270e1a461f7ea9157f71aecf42 Mon Sep 17 00:00:00 2001
From: Chris Beer <cabeer@stanford.edu>
Date: Thu, 14 Mar 2019 14:38:44 -0700
Subject: [PATCH] Pan the elastic workspace to the focused window when selected
 in the window list

---
 __tests__/src/actions/window.test.js        | 49 +++++++++++++++++++++
 __tests__/src/components/WindowList.test.js |  2 +-
 __tests__/src/reducers/workspace.test.js    | 11 +++++
 src/components/WindowList.js                |  2 +-
 src/state/actions/window.js                 | 13 +++++-
 src/state/reducers/workspace.js             |  9 +++-
 6 files changed, 81 insertions(+), 5 deletions(-)

diff --git a/__tests__/src/actions/window.test.js b/__tests__/src/actions/window.test.js
index 89889961e..0eeb46dbe 100644
--- a/__tests__/src/actions/window.test.js
+++ b/__tests__/src/actions/window.test.js
@@ -2,6 +2,55 @@ import * as actions from '../../../src/state/actions';
 import ActionTypes from '../../../src/state/actions/action-types';
 
 describe('window actions', () => {
+  describe('focusWindow', () => {
+    it('should return correct action object with pan=true', () => {
+      const expectedAction = {
+        type: ActionTypes.FOCUS_WINDOW,
+        windowId: 'window',
+        position: { x: -150, y: -188 },
+      };
+
+      const mockState = {
+        windows: {
+          window: { x: 50, y: 12 },
+        },
+        companionWindows: {},
+      };
+
+      const mockDispatch = jest.fn(() => ({}));
+      const mockGetState = jest.fn(() => mockState);
+      const thunk = actions.focusWindow('window', true);
+
+      thunk(mockDispatch, mockGetState);
+
+      const action = mockDispatch.mock.calls[0][0];
+      expect(action).toEqual(expectedAction);
+    });
+    it('should return correct action object with pan=false', () => {
+      const expectedAction = {
+        type: ActionTypes.FOCUS_WINDOW,
+        windowId: 'window',
+        position: {},
+      };
+
+      const mockState = {
+        windows: {
+          window: { x: 50, y: 12 },
+        },
+        companionWindows: {},
+      };
+
+      const mockDispatch = jest.fn(() => ({}));
+      const mockGetState = jest.fn(() => mockState);
+      const thunk = actions.focusWindow('window');
+
+      thunk(mockDispatch, mockGetState);
+
+      const action = mockDispatch.mock.calls[0][0];
+      expect(action).toEqual(expectedAction);
+    });
+  });
+
   describe('addWindow', () => {
     it('should create a new window with merged defaults', () => {
       const options = {
diff --git a/__tests__/src/components/WindowList.test.js b/__tests__/src/components/WindowList.test.js
index ab552b1f3..f41fe0e60 100644
--- a/__tests__/src/components/WindowList.test.js
+++ b/__tests__/src/components/WindowList.test.js
@@ -56,7 +56,7 @@ describe('WindowList', () => {
       ).toBe(true);
       wrapper.find('WithStyles(MenuItem)').simulate('click', {});
       expect(handleClose).toBeCalled();
-      expect(focusWindow).toBeCalledWith('xyz');
+      expect(focusWindow).toBeCalledWith('xyz', true);
     });
   });
 
diff --git a/__tests__/src/reducers/workspace.test.js b/__tests__/src/reducers/workspace.test.js
index 9873e434d..e4d40d594 100644
--- a/__tests__/src/reducers/workspace.test.js
+++ b/__tests__/src/reducers/workspace.test.js
@@ -2,12 +2,23 @@ import { workspaceReducer } from '../../../src/state/reducers/workspace';
 import ActionTypes from '../../../src/state/actions/action-types';
 
 describe('workspace reducer', () => {
+  it('should handle FOCUS_WINDOW without position coordinates', () => {
+    expect(workspaceReducer([], {
+      type: ActionTypes.FOCUS_WINDOW,
+      windowId: 'abc123',
+    })).toEqual({
+      focusedWindowId: 'abc123',
+      viewportPosition: {},
+    });
+  });
   it('should handle FOCUS_WINDOW', () => {
     expect(workspaceReducer([], {
       type: ActionTypes.FOCUS_WINDOW,
       windowId: 'abc123',
+      position: { x: 10, y: 50 },
     })).toEqual({
       focusedWindowId: 'abc123',
+      viewportPosition: { x: 10, y: 50 },
     });
   });
   it('should handle SET_WORKSPACE_FULLSCREEN', () => {
diff --git a/src/components/WindowList.js b/src/components/WindowList.js
index d82c67f27..193e3e359 100644
--- a/src/components/WindowList.js
+++ b/src/components/WindowList.js
@@ -48,7 +48,7 @@ export class WindowList extends Component {
             <MenuItem
               key={window.id}
               selected={i === 0}
-              onClick={(e) => { focusWindow(window.id); handleClose(e); }}
+              onClick={(e) => { focusWindow(window.id, true); handleClose(e); }}
             >
               <Typography variant="body1">
                 {
diff --git a/src/state/actions/window.js b/src/state/actions/window.js
index 2aaf858d9..9a58008f5 100644
--- a/src/state/actions/window.js
+++ b/src/state/actions/window.js
@@ -7,8 +7,17 @@ import ActionTypes from './action-types';
  * @param  {String} windowId
  * @memberof ActionCreators
  */
-export function focusWindow(windowId) {
-  return { type: ActionTypes.FOCUS_WINDOW, windowId };
+export function focusWindow(windowId, pan = false) {
+  return (dispatch, getState) => {
+    const { windows } = getState();
+    const { x, y } = windows[windowId];
+
+    dispatch({
+      type: ActionTypes.FOCUS_WINDOW,
+      windowId,
+      position: pan ? { x: x - 200, y: y - 200 } : {},
+    });
+  };
 }
 
 /**
diff --git a/src/state/reducers/workspace.js b/src/state/reducers/workspace.js
index 0db841dc2..d9606abd8 100644
--- a/src/state/reducers/workspace.js
+++ b/src/state/reducers/workspace.js
@@ -17,7 +17,14 @@ export const workspaceReducer = (
 ) => {
   switch (action.type) {
     case ActionTypes.FOCUS_WINDOW:
-      return { ...state, focusedWindowId: action.windowId };
+      return {
+        ...state,
+        focusedWindowId: action.windowId,
+        viewportPosition: {
+          ...state.viewportPosition,
+          ...action.position,
+        },
+      };
     case ActionTypes.SET_WORKSPACE_FULLSCREEN:
       return { ...state, isFullscreenEnabled: action.isFullscreenEnabled };
     case ActionTypes.TOGGLE_ZOOM_CONTROLS:
-- 
GitLab