Skip to content

Commit

Permalink
[DevTools] Added resize support for Components panel. (#18046)
Browse files Browse the repository at this point in the history
* feat: DevTools - Added Resize Support.

* feat: Prettier.

* feat: DevTools - Added debug comments.

* feat: DevTools - Removed Use Memo.

* feat: DevTools - Added types.

* feat: DevTools - Extracted values to constants.

* feat: DevTools - Removed useCallback.

* feat: DevTools - Finished refactoring.

* feat: DevTools - Merging fixup.

* feat: DevTools - Prettier fix.

* feat: DevTools - Extracted code from Components fil.

* feat: DevTools - Fixed orientation change issue.

* feat: DevTools - Added flow types for reducer and refs.

* feat: DevTools - Fixed orientation change on initial load.

* Update packages/react-devtools-shared/src/devtools/views/Components/ComponentsResizer.js

* Removed unused `orientationRef`

* Fix Flow ref issue

Co-authored-by: Brian Vaughn <brian.david.vaughn@gmail.com>
  • Loading branch information
hristo-kanchev and bvaughn committed Feb 26, 2020
1 parent e1c7e65 commit d166319
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -1,36 +1,42 @@
.Components {
position: relative;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
background-color: var(--color-background);
color: var(--color-text);
font-family: var(--font-family-sans);
}

.TreeWrapper {
flex: 0 0 65%;
flex: 0 0 var(--horizontal-resize-percentage);
overflow: auto;
}

.SelectedElementWrapper {
flex: 0 0 35%;
flex: 1 1 35%;
overflow-x: hidden;
overflow-y: auto;
}

@media screen and (max-width: 600px) {
.Components {
flex-direction: column;
}
.ResizeBarWrapper {
flex: 0 0 0px;
position: relative;
}

.ResizeBar {
position: absolute;
left: -2px;
width: 5px;
height: 100%;
cursor: ew-resize;
}

@media screen and (max-width: 600px) {
.TreeWrapper {
flex: 0 0 50%;
flex: 0 0 var(--vertical-resize-percentage);
}

.SelectedElementWrapper {
flex: 0 0 50%;
flex: 1 1 50%;
}

.ResizeBar {
top: -2px;
left: 0;
width: 100%;
height: 5px;
cursor: ns-resize;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
*/

import * as React from 'react';
import {Suspense} from 'react';
import {Suspense, Fragment} from 'react';
import Tree from './Tree';
import SelectedElement from './SelectedElement';
import {InspectedElementContextController} from './InspectedElementContext';
import {NativeStyleContextController} from './NativeStyleEditor/context';
import {OwnersListContextController} from './OwnersListContext';
import ComponentsResizer from './ComponentsResizer';
import portaledContent from '../portaledContent';
import {ModalDialog} from '../ModalDialog';
import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal';
Expand All @@ -22,25 +23,34 @@ import {SettingsModalContextController} from 'react-devtools-shared/src/devtools
import styles from './Components.css';

function Components(_: {||}) {
// TODO Flex wrappers below should be user resizable.
return (
<SettingsModalContextController>
<OwnersListContextController>
<InspectedElementContextController>
<div className={styles.Components}>
<div className={styles.TreeWrapper}>
<Tree />
</div>
<div className={styles.SelectedElementWrapper}>
<NativeStyleContextController>
<Suspense fallback={<Loading />}>
<SelectedElement />
</Suspense>
</NativeStyleContextController>
</div>
<ModalDialog />
<SettingsModal />
</div>
<ComponentsResizer>
{({resizeElementRef, onResizeStart}) => (
<Fragment>
<div ref={resizeElementRef} className={styles.TreeWrapper}>
<Tree />
</div>
<div className={styles.ResizeBarWrapper}>
<div
onMouseDown={onResizeStart}
className={styles.ResizeBar}
/>
</div>
<div className={styles.SelectedElementWrapper}>
<NativeStyleContextController>
<Suspense fallback={<Loading />}>
<SelectedElement />
</Suspense>
</NativeStyleContextController>
</div>
<ModalDialog />
<SettingsModal />
</Fragment>
)}
</ComponentsResizer>
</InspectedElementContextController>
</OwnersListContextController>
</SettingsModalContextController>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.ComponentsWrapper {
position: relative;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
background-color: var(--color-background);
color: var(--color-text);
font-family: var(--font-family-sans);
}

@media screen and (max-width: 600px) {
.ComponentsWrapper {
flex-direction: column;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import * as React from 'react';
import {useEffect, useLayoutEffect, useReducer, useRef} from 'react';
import {
localStorageGetItem,
localStorageSetItem,
} from 'react-devtools-shared/src/storage';
import styles from './ComponentsResizer.css';

const LOCAL_STORAGE_KEY = 'React::DevTools::createResizeReducer';
const VERTICAL_MODE_MAX_WIDTH = 600;
const MINIMUM_SIZE = 50;

type Orientation = 'horizontal' | 'vertical';

type ResizeActionType =
| 'ACTION_SET_DID_MOUNT'
| 'ACTION_SET_IS_RESIZING'
| 'ACTION_SET_HORIZONTAL_PERCENTAGE'
| 'ACTION_SET_VERTICAL_PERCENTAGE';

type ResizeAction = {|
type: ResizeActionType,
payload: any,
|};

type ResizeState = {|
horizontalPercentage: number,
isResizing: boolean,
verticalPercentage: number,
|};

function initResizeState(): ResizeState {
let horizontalPercentage = 0.65;
let verticalPercentage = 0.5;

try {
let data = localStorageGetItem(LOCAL_STORAGE_KEY);
if (data != null) {
data = JSON.parse(data);
horizontalPercentage = data.horizontalPercentage;
verticalPercentage = data.verticalPercentage;
}
} catch (error) {}

return {
horizontalPercentage,
isResizing: false,
verticalPercentage,
};
}

function resizeReducer(state: ResizeState, action: ResizeAction): ResizeState {
switch (action.type) {
case 'ACTION_SET_IS_RESIZING':
return {
...state,
isResizing: action.payload,
};
case 'ACTION_SET_HORIZONTAL_PERCENTAGE':
return {
...state,
horizontalPercentage: action.payload,
};
case 'ACTION_SET_VERTICAL_PERCENTAGE':
return {
...state,
verticalPercentage: action.payload,
};
default:
return state;
}
}

type Props = {|
children: ({
resizeElementRef: React$Ref<HTMLElement>,
onResizeStart: () => void,
}) => React$Node,
|};

export default function ComponentsResizer({children}: Props) {
const wrapperElementRef = useRef<HTMLElement>(null);
const resizeElementRef = useRef<HTMLElement>(null);

const [state, dispatch] = useReducer<ResizeState, ResizeAction>(
resizeReducer,
null,
initResizeState,
);

const {horizontalPercentage, verticalPercentage} = state;

useLayoutEffect(() => {
const resizeElement = resizeElementRef.current;

setResizeCSSVariable(
resizeElement,
'horizontal',
horizontalPercentage * 100,
);
setResizeCSSVariable(resizeElement, 'vertical', verticalPercentage * 100);
}, []);

useEffect(() => {
const timeoutID = setTimeout(() => {
localStorageSetItem(
LOCAL_STORAGE_KEY,
JSON.stringify({
horizontalPercentage,
verticalPercentage,
}),
);
}, 500);

return () => clearTimeout(timeoutID);
}, [horizontalPercentage, verticalPercentage]);

const {isResizing} = state;

const onResizeStart = () =>
dispatch({type: 'ACTION_SET_IS_RESIZING', payload: true});
const onResizeEnd = () =>
dispatch({type: 'ACTION_SET_IS_RESIZING', payload: false});

const onResize = event => {
const resizeElement = resizeElementRef.current;
const wrapperElement = wrapperElementRef.current;

if (!isResizing || wrapperElement === null || resizeElement === null) {
return;
}

event.preventDefault();

const orientation = getOrientation(wrapperElement);

const {height, width, left, top} = wrapperElement.getBoundingClientRect();

const currentMousePosition =
orientation === 'horizontal' ? event.clientX - left : event.clientY - top;

const boundaryMin = MINIMUM_SIZE;
const boundaryMax =
orientation === 'horizontal'
? width - MINIMUM_SIZE
: height - MINIMUM_SIZE;

const isMousePositionInBounds =
currentMousePosition > boundaryMin && currentMousePosition < boundaryMax;

if (isMousePositionInBounds) {
const resizedElementDimension =
orientation === 'horizontal' ? width : height;
const actionType =
orientation === 'horizontal'
? 'ACTION_SET_HORIZONTAL_PERCENTAGE'
: 'ACTION_SET_VERTICAL_PERCENTAGE';
const percentage = (currentMousePosition / resizedElementDimension) * 100;

setResizeCSSVariable(resizeElement, orientation, percentage);

dispatch({
type: actionType,
payload: currentMousePosition / resizedElementDimension,
});
}
};

return (
<div
ref={wrapperElementRef}
className={styles.ComponentsWrapper}
{...(isResizing && {
onMouseMove: onResize,
onMouseLeave: onResizeEnd,
onMouseUp: onResizeEnd,
})}>
{children({resizeElementRef, onResizeStart})}
</div>
);
}

function getOrientation(
wrapperElement: null | HTMLElement,
): null | Orientation {
if (wrapperElement != null) {
const {width} = wrapperElement.getBoundingClientRect();
return width > VERTICAL_MODE_MAX_WIDTH ? 'horizontal' : 'vertical';
}
return null;
}

function setResizeCSSVariable(
resizeElement: null | HTMLElement,
orientation: null | Orientation,
percentage: number,
): void {
if (resizeElement !== null && orientation !== null) {
resizeElement.style.setProperty(
`--${orientation}-resize-percentage`,
`${percentage}%`,
);
}
}

0 comments on commit d166319

Please sign in to comment.