mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[DevTools] Layout for Suspense tab (#34042)
This commit is contained in:
@@ -34,6 +34,12 @@ export type IconType =
|
||||
| 'save'
|
||||
| 'search'
|
||||
| 'settings'
|
||||
| 'panel-left-close'
|
||||
| 'panel-left-open'
|
||||
| 'panel-right-close'
|
||||
| 'panel-right-open'
|
||||
| 'panel-bottom-open'
|
||||
| 'panel-bottom-close'
|
||||
| 'error'
|
||||
| 'suspend'
|
||||
| 'undo'
|
||||
@@ -46,8 +52,10 @@ type Props = {
|
||||
type: IconType,
|
||||
};
|
||||
|
||||
const materialIconsViewBox = '0 -960 960 960';
|
||||
export default function ButtonIcon({className = '', type}: Props): React.Node {
|
||||
let pathData = null;
|
||||
let viewBox = '0 0 24 24';
|
||||
switch (type) {
|
||||
case 'add':
|
||||
pathData = PATH_ADD;
|
||||
@@ -121,6 +129,30 @@ export default function ButtonIcon({className = '', type}: Props): React.Node {
|
||||
case 'error':
|
||||
pathData = PATH_ERROR;
|
||||
break;
|
||||
case 'panel-left-close':
|
||||
pathData = PATH_MATERIAL_PANEL_LEFT_CLOSE;
|
||||
viewBox = materialIconsViewBox;
|
||||
break;
|
||||
case 'panel-left-open':
|
||||
pathData = PATH_MATERIAL_PANEL_LEFT_OPEN;
|
||||
viewBox = materialIconsViewBox;
|
||||
break;
|
||||
case 'panel-right-close':
|
||||
pathData = PATH_MATERIAL_PANEL_RIGHT_CLOSE;
|
||||
viewBox = materialIconsViewBox;
|
||||
break;
|
||||
case 'panel-right-open':
|
||||
pathData = PATH_MATERIAL_PANEL_RIGHT_OPEN;
|
||||
viewBox = materialIconsViewBox;
|
||||
break;
|
||||
case 'panel-bottom-open':
|
||||
pathData = PATH_MATERIAL_PANEL_BOTTOM_OPEN;
|
||||
viewBox = materialIconsViewBox;
|
||||
break;
|
||||
case 'panel-bottom-close':
|
||||
pathData = PATH_MATERIAL_PANEL_BOTTOM_CLOSE;
|
||||
viewBox = materialIconsViewBox;
|
||||
break;
|
||||
case 'suspend':
|
||||
pathData = PATH_SUSPEND;
|
||||
break;
|
||||
@@ -147,7 +179,7 @@ export default function ButtonIcon({className = '', type}: Props): React.Node {
|
||||
className={`${styles.ButtonIcon} ${className}`}
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24">
|
||||
viewBox={viewBox}>
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
{typeof pathData === 'string' ? (
|
||||
<path fill="currentColor" d={pathData} />
|
||||
@@ -276,3 +308,33 @@ const PATH_VIEW_SOURCE = `
|
||||
const PATH_EDITOR = `
|
||||
M7 5h10v2h2V3c0-1.1-.9-1.99-2-1.99L7 1c-1.1 0-2 .9-2 2v4h2V5zm8.41 11.59L20 12l-4.59-4.59L14 8.83 17.17 12 14 15.17l1.41 1.42zM10 15.17L6.83 12 10 8.83 8.59 7.41 4 12l4.59 4.59L10 15.17zM17 19H7v-2H5v4c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2v-4h-2v2z
|
||||
`;
|
||||
|
||||
// Source: Material Design Icons left_panel_close
|
||||
const PATH_MATERIAL_PANEL_LEFT_CLOSE = `
|
||||
M648-324v-312L480-480l168 156ZM211-144q-27.64 0-47.32-19.68T144-211v-538q0-27.64 19.68-47.32T211-816h538q27.64 0 47.32 19.68T816-749v538q0 27.64-19.68 47.32T749-144H211Zm125-72v-528H216v528h120Zm72 0h336v-528H408v528Zm-72 0H216h120Z
|
||||
`;
|
||||
|
||||
// Source: Material Design Icons left_panel_open
|
||||
const PATH_MATERIAL_PANEL_LEFT_OPEN = `
|
||||
M504-595v230q0 12.25 10.5 16.62Q525-344 534-352l110-102q11-11.18 11-26.09T644-506L534-608q-8.82-8-19.41-3.5T504-595ZM211-144q-27.64 0-47.32-19.68T144-211v-538q0-27.64 19.68-47.32T211-816h538q27.64 0 47.32 19.68T816-749v538q0 27.64-19.68 47.32T749-144H211Zm125-72v-528H216v528h120Zm72 0h336v-528H408v528Zm-72 0H216h120Z
|
||||
`;
|
||||
|
||||
// Source: Material Design Icons right_panel_close
|
||||
const PATH_MATERIAL_PANEL_RIGHT_CLOSE = `
|
||||
M312-365q0 12.25 10.5 16.62Q333-344 342-352l110-102q11-11.18 11-26.09T452-506L342-608q-8.82-8-19.41-3.5T312-595v230ZM211-144q-27.64 0-47.32-19.68T144-211v-538q0-27.64 19.68-47.32T211-816h538q27.64 0 47.32 19.68T816-749v538q0 27.64-19.68 47.32T749-144H211Zm413-72h120v-528H624v528Zm-72 0v-528H216v528h336Zm72 0h120-120Z
|
||||
`;
|
||||
|
||||
// Source: Material Design Icons right_panel_open
|
||||
const PATH_MATERIAL_PANEL_RIGHT_OPEN = `
|
||||
M456-365v-230q0-12.25-10.5-16.63Q435-616 426-608L316-506q-11 11.18-11 26.09T316-454l110 102q8.82 8 19.41 3.5T456-365ZM211-144q-27.64 0-47.32-19.68T144-211v-538q0-27.64 19.68-47.32T211-816h538q27.64 0 47.32 19.68T816-749v538q0 27.64-19.68 47.32T749-144H211Zm413-72h120v-528H624v528Zm-72 0v-528H216v528h336Zm72 0h120-120Z
|
||||
`;
|
||||
|
||||
// Source: Material Design Icons bottom_panel_open
|
||||
const PATH_MATERIAL_PANEL_BOTTOM_OPEN = `
|
||||
M365-504h230q12.25 0 16.63-10.5Q616-525 608-534L506-644q-11.18-11-26.09-11T454-644L352-534q-8 8.82-3.5 19.41T365-504ZM211-144q-27.64 0-47.32-19.68T144-211v-538q0-27.64 19.68-47.32T211-816h538q27.64 0 47.32 19.68T816-749v538q0 27.64-19.68 47.32T749-144H211Zm5-192v120h528v-120H216Zm0-72h528v-336H216v336Zm0 72v120-120Z
|
||||
`;
|
||||
|
||||
// Source: Material Design Icons bottom_panel_close
|
||||
const PATH_MATERIAL_PANEL_BOTTOM_CLOSE = `
|
||||
m506-508 102-110q8-8.82 3.5-19.41T595-648H365q-12.25 0-16.62 10.5Q344-627 352-618l102 110q11.18 11 26.09 11T506-508Zm243-308q27.64 0 47.32 19.68T816-749v538q0 27.64-19.68 47.32T749-144H211q-27.64 0-47.32-19.68T144-211v-538q0-27.64 19.68-47.32T211-816h538ZM216-336v120h528v-120H216Zm528-72v-336H216v336h528Zm-528 72v120-120Z
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
.SuspenseTab {
|
||||
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);
|
||||
}
|
||||
|
||||
.SuspenseTab, .SuspenseTab * {
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: var(--font-smoothing);
|
||||
}
|
||||
|
||||
.TreeWrapper {
|
||||
flex: 1 1 var(--horizontal-resize-tree-percentage);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: auto;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.InspectedElementWrapper {
|
||||
flex: 1 1 calc(100% - var(--horizontal-resize-tree-percentage));
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ResizeBarWrapper {
|
||||
flex: 0 0 0px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ResizeBar {
|
||||
position: absolute;
|
||||
/*
|
||||
* moving the bar out of its bounding box might cause its hitbox to overlap
|
||||
* with another scrollbar creating disorienting UX where you both resize and scroll
|
||||
* at the same time.
|
||||
* If you adjust this value, double check that starting resize right on this edge
|
||||
* doesn't also cause scroll
|
||||
*/
|
||||
left: 1px;
|
||||
width: 5px;
|
||||
height: 100%;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.TreeView footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@container devtools (width < 600px) {
|
||||
.SuspenseTab {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.TreeWrapper {
|
||||
border-top: 1px solid var(--color-border);
|
||||
flex: 1 0 var(--vertical-resize-tree-percentage);
|
||||
}
|
||||
|
||||
.InspectedElementWrapper {
|
||||
flex: 1 1 50%;
|
||||
}
|
||||
|
||||
.TreeWrapper + .ResizeBarWrapper .ResizeBar {
|
||||
top: 1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.TreeView footer {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.ToggleInspectedElement[data-orientation="horizontal"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.TreeList {
|
||||
flex: 0 0 var(--horizontal-resize-tree-list-percentage);
|
||||
border-right: 1px solid var(--color-border);
|
||||
padding: 0.25rem
|
||||
}
|
||||
|
||||
.TreeView {
|
||||
flex: 1 1 35%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.Rects {
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding: 0.25rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.TimelineWrapper {
|
||||
padding: 0.25rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.Timeline {
|
||||
flex-grow: 1;
|
||||
}
|
||||
+442
-5
@@ -1,8 +1,445 @@
|
||||
import * as React from 'react';
|
||||
import portaledContent from '../portaledContent';
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
function SuspenseTab() {
|
||||
return 'Under construction';
|
||||
import * as React from 'react';
|
||||
import {useEffect, useLayoutEffect, useReducer, useRef} from 'react';
|
||||
|
||||
import {
|
||||
localStorageGetItem,
|
||||
localStorageSetItem,
|
||||
} from 'react-devtools-shared/src/storage';
|
||||
import ButtonIcon, {type IconType} from '../ButtonIcon';
|
||||
import InspectedElementErrorBoundary from '../Components/InspectedElementErrorBoundary';
|
||||
import InspectedElement from '../Components/InspectedElement';
|
||||
import portaledContent from '../portaledContent';
|
||||
import styles from './SuspenseTab.css';
|
||||
import Button from '../Button';
|
||||
|
||||
type Orientation = 'horizontal' | 'vertical';
|
||||
|
||||
type LayoutActionType =
|
||||
| 'ACTION_SET_TREE_LIST_TOGGLE'
|
||||
| 'ACTION_SET_TREE_LIST_HORIZONTAL_FRACTION'
|
||||
| 'ACTION_SET_INSPECTED_ELEMENT_TOGGLE'
|
||||
| 'ACTION_SET_INSPECTED_ELEMENT_HORIZONTAL_FRACTION'
|
||||
| 'ACTION_SET_INSPECTED_ELEMENT_VERTICAL_FRACTION';
|
||||
type LayoutAction = {
|
||||
type: LayoutActionType,
|
||||
payload: any,
|
||||
};
|
||||
|
||||
type LayoutState = {
|
||||
treeListHidden: boolean,
|
||||
treeListHorizontalFraction: number,
|
||||
inspectedElementHidden: boolean,
|
||||
inspectedElementHorizontalFraction: number,
|
||||
inspectedElementVerticalFraction: number,
|
||||
};
|
||||
type LayoutDispatch = (action: LayoutAction) => void;
|
||||
|
||||
function SuspenseTreeList() {
|
||||
return <div>tree list</div>;
|
||||
}
|
||||
|
||||
export default (portaledContent(SuspenseTab): React.ComponentType<{}>);
|
||||
function SuspenseTimeline() {
|
||||
return <div className={styles.Timeline}>timeline</div>;
|
||||
}
|
||||
|
||||
function SuspenseRects() {
|
||||
return <div>rects</div>;
|
||||
}
|
||||
|
||||
function ToggleTreeList({
|
||||
dispatch,
|
||||
state,
|
||||
}: {
|
||||
dispatch: LayoutDispatch,
|
||||
state: LayoutState,
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
onClick={() =>
|
||||
dispatch({
|
||||
type: 'ACTION_SET_TREE_LIST_TOGGLE',
|
||||
payload: null,
|
||||
})
|
||||
}
|
||||
title={state.treeListHidden ? 'Show Tree List' : 'Hide Tree List'}>
|
||||
<ButtonIcon
|
||||
type={state.treeListHidden ? 'panel-left-open' : 'panel-left-close'}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function ToggleInspectedElement({
|
||||
dispatch,
|
||||
state,
|
||||
orientation,
|
||||
}: {
|
||||
dispatch: LayoutDispatch,
|
||||
state: LayoutState,
|
||||
orientation: 'horizontal' | 'vertical',
|
||||
}) {
|
||||
let iconType: IconType;
|
||||
if (orientation === 'horizontal') {
|
||||
iconType = state.inspectedElementHidden
|
||||
? 'panel-right-open'
|
||||
: 'panel-right-close';
|
||||
} else {
|
||||
iconType = state.inspectedElementHidden
|
||||
? 'panel-bottom-open'
|
||||
: 'panel-bottom-close';
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
className={styles.ToggleInspectedElement}
|
||||
data-orientation={orientation}
|
||||
onClick={() =>
|
||||
dispatch({
|
||||
type: 'ACTION_SET_INSPECTED_ELEMENT_TOGGLE',
|
||||
payload: null,
|
||||
})
|
||||
}
|
||||
title={
|
||||
state.inspectedElementHidden
|
||||
? 'Show Inspected Element'
|
||||
: 'Hide Inspected Element'
|
||||
}>
|
||||
<ButtonIcon type={iconType} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function SuspenseTab(_: {}) {
|
||||
const [state, dispatch] = useReducer<LayoutState, null, LayoutAction>(
|
||||
layoutReducer,
|
||||
null,
|
||||
initLayoutState,
|
||||
);
|
||||
|
||||
const wrapperTreeRef = useRef<null | HTMLElement>(null);
|
||||
const resizeTreeRef = useRef<null | HTMLElement>(null);
|
||||
const resizeTreeListRef = useRef<null | HTMLElement>(null);
|
||||
|
||||
// TODO: We'll show the recently inspected element in this tab when it should probably
|
||||
// switch to the nearest Suspense boundary when we switch into this tab.
|
||||
|
||||
const {
|
||||
inspectedElementHidden,
|
||||
inspectedElementHorizontalFraction,
|
||||
inspectedElementVerticalFraction,
|
||||
treeListHidden,
|
||||
treeListHorizontalFraction,
|
||||
} = state;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const wrapperElement = wrapperTreeRef.current;
|
||||
|
||||
setResizeCSSVariable(
|
||||
wrapperElement,
|
||||
'tree',
|
||||
'horizontal',
|
||||
inspectedElementHorizontalFraction * 100,
|
||||
);
|
||||
setResizeCSSVariable(
|
||||
wrapperElement,
|
||||
'tree',
|
||||
'vertical',
|
||||
inspectedElementVerticalFraction * 100,
|
||||
);
|
||||
|
||||
const resizeTreeListElement = resizeTreeListRef.current;
|
||||
setResizeCSSVariable(
|
||||
resizeTreeListElement,
|
||||
'tree-list',
|
||||
'horizontal',
|
||||
treeListHorizontalFraction * 100,
|
||||
);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const timeoutID = setTimeout(() => {
|
||||
localStorageSetItem(
|
||||
LOCAL_STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
inspectedElementHidden,
|
||||
inspectedElementHorizontalFraction,
|
||||
inspectedElementVerticalFraction,
|
||||
treeListHidden,
|
||||
treeListHorizontalFraction,
|
||||
}),
|
||||
);
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timeoutID);
|
||||
}, [
|
||||
inspectedElementHidden,
|
||||
inspectedElementHorizontalFraction,
|
||||
inspectedElementVerticalFraction,
|
||||
treeListHidden,
|
||||
treeListHorizontalFraction,
|
||||
]);
|
||||
|
||||
const onResizeStart = (event: SyntheticPointerEvent<HTMLElement>) => {
|
||||
const element = event.currentTarget;
|
||||
element.setPointerCapture(event.pointerId);
|
||||
};
|
||||
|
||||
const onResizeEnd = (event: SyntheticPointerEvent<HTMLElement>) => {
|
||||
const element = event.currentTarget;
|
||||
element.releasePointerCapture(event.pointerId);
|
||||
};
|
||||
|
||||
const onResizeTree = (event: SyntheticPointerEvent<HTMLElement>) => {
|
||||
const element = event.currentTarget;
|
||||
const isResizing = element.hasPointerCapture(event.pointerId);
|
||||
if (!isResizing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resizeElement = resizeTreeRef.current;
|
||||
const wrapperElement = wrapperTreeRef.current;
|
||||
|
||||
if (wrapperElement === null || resizeElement === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const orientation = getTreeOrientation(wrapperElement);
|
||||
|
||||
const {height, width, left, top} = wrapperElement.getBoundingClientRect();
|
||||
|
||||
const currentMousePosition =
|
||||
orientation === 'horizontal' ? event.clientX - left : event.clientY - top;
|
||||
|
||||
const boundaryMin = MINIMUM_TREE_SIZE;
|
||||
const boundaryMax =
|
||||
orientation === 'horizontal'
|
||||
? width - MINIMUM_TREE_SIZE
|
||||
: height - MINIMUM_TREE_SIZE;
|
||||
|
||||
const isMousePositionInBounds =
|
||||
currentMousePosition > boundaryMin && currentMousePosition < boundaryMax;
|
||||
|
||||
if (isMousePositionInBounds) {
|
||||
const resizedElementDimension =
|
||||
orientation === 'horizontal' ? width : height;
|
||||
const actionType =
|
||||
orientation === 'horizontal'
|
||||
? 'ACTION_SET_INSPECTED_ELEMENT_HORIZONTAL_FRACTION'
|
||||
: 'ACTION_SET_INSPECTED_ELEMENT_VERTICAL_FRACTION';
|
||||
const fraction = currentMousePosition / resizedElementDimension;
|
||||
const percentage = fraction * 100;
|
||||
|
||||
setResizeCSSVariable(wrapperElement, 'tree', orientation, percentage);
|
||||
|
||||
dispatch({
|
||||
type: actionType,
|
||||
payload: fraction,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onResizeTreeList = (event: SyntheticPointerEvent<HTMLElement>) => {
|
||||
const element = event.currentTarget;
|
||||
const isResizing = element.hasPointerCapture(event.pointerId);
|
||||
if (!isResizing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resizeElement = resizeTreeListRef.current;
|
||||
const wrapperElement = resizeTreeRef.current;
|
||||
|
||||
if (wrapperElement === null || resizeElement === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const orientation = 'horizontal';
|
||||
|
||||
const {height, width, left, top} = wrapperElement.getBoundingClientRect();
|
||||
|
||||
const currentMousePosition =
|
||||
orientation === 'horizontal' ? event.clientX - left : event.clientY - top;
|
||||
|
||||
const boundaryMin = MINIMUM_TREE_LIST_SIZE;
|
||||
const boundaryMax =
|
||||
orientation === 'horizontal'
|
||||
? width - MINIMUM_TREE_LIST_SIZE
|
||||
: height - MINIMUM_TREE_LIST_SIZE;
|
||||
|
||||
const isMousePositionInBounds =
|
||||
currentMousePosition > boundaryMin && currentMousePosition < boundaryMax;
|
||||
|
||||
if (isMousePositionInBounds) {
|
||||
const resizedElementDimension =
|
||||
orientation === 'horizontal' ? width : height;
|
||||
const actionType = 'ACTION_SET_TREE_LIST_HORIZONTAL_FRACTION';
|
||||
const percentage = (currentMousePosition / resizedElementDimension) * 100;
|
||||
|
||||
setResizeCSSVariable(resizeElement, 'tree-list', orientation, percentage);
|
||||
|
||||
dispatch({
|
||||
type: actionType,
|
||||
payload: currentMousePosition / resizedElementDimension,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.SuspenseTab} ref={wrapperTreeRef}>
|
||||
<div className={styles.TreeWrapper} ref={resizeTreeRef}>
|
||||
<div
|
||||
className={styles.TreeList}
|
||||
hidden={treeListHidden}
|
||||
ref={resizeTreeListRef}>
|
||||
<SuspenseTreeList />
|
||||
</div>
|
||||
<div className={styles.ResizeBarWrapper}>
|
||||
<div
|
||||
onPointerDown={onResizeStart}
|
||||
onPointerMove={onResizeTreeList}
|
||||
onPointerUp={onResizeEnd}
|
||||
className={styles.ResizeBar}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.TreeView}>
|
||||
<div className={styles.TimelineWrapper}>
|
||||
<ToggleTreeList dispatch={dispatch} state={state} />
|
||||
<SuspenseTimeline />
|
||||
<ToggleInspectedElement
|
||||
dispatch={dispatch}
|
||||
state={state}
|
||||
orientation="horizontal"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.Rects}>
|
||||
<SuspenseRects />
|
||||
</div>
|
||||
<footer>
|
||||
<ToggleInspectedElement
|
||||
dispatch={dispatch}
|
||||
state={state}
|
||||
orientation="vertical"
|
||||
/>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.ResizeBarWrapper}>
|
||||
<div
|
||||
onPointerDown={onResizeStart}
|
||||
onPointerMove={onResizeTree}
|
||||
onPointerUp={onResizeEnd}
|
||||
className={styles.ResizeBar}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={styles.InspectedElementWrapper}
|
||||
hidden={inspectedElementHidden}>
|
||||
<InspectedElementErrorBoundary>
|
||||
<InspectedElement />
|
||||
</InspectedElementErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const LOCAL_STORAGE_KEY = 'React::DevTools::SuspenseTab::layout';
|
||||
const VERTICAL_TREE_MODE_MAX_WIDTH = 600;
|
||||
const MINIMUM_TREE_SIZE = 100;
|
||||
const MINIMUM_TREE_LIST_SIZE = 100;
|
||||
|
||||
function layoutReducer(state: LayoutState, action: LayoutAction): LayoutState {
|
||||
switch (action.type) {
|
||||
case 'ACTION_SET_TREE_LIST_TOGGLE':
|
||||
return {
|
||||
...state,
|
||||
treeListHidden: !state.treeListHidden,
|
||||
};
|
||||
case 'ACTION_SET_TREE_LIST_HORIZONTAL_FRACTION':
|
||||
return {
|
||||
...state,
|
||||
treeListHorizontalFraction: action.payload,
|
||||
};
|
||||
case 'ACTION_SET_INSPECTED_ELEMENT_TOGGLE':
|
||||
return {
|
||||
...state,
|
||||
inspectedElementHidden: !state.inspectedElementHidden,
|
||||
};
|
||||
case 'ACTION_SET_INSPECTED_ELEMENT_HORIZONTAL_FRACTION':
|
||||
return {
|
||||
...state,
|
||||
inspectedElementHorizontalFraction: action.payload,
|
||||
};
|
||||
case 'ACTION_SET_INSPECTED_ELEMENT_VERTICAL_FRACTION':
|
||||
return {
|
||||
...state,
|
||||
inspectedElementVerticalFraction: action.payload,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function initLayoutState(): LayoutState {
|
||||
let inspectedElementHidden = false;
|
||||
let inspectedElementHorizontalFraction = 0.65;
|
||||
let inspectedElementVerticalFraction = 0.5;
|
||||
let treeListHidden = false;
|
||||
let treeListHorizontalFraction = 0.35;
|
||||
|
||||
try {
|
||||
let data = localStorageGetItem(LOCAL_STORAGE_KEY);
|
||||
if (data != null) {
|
||||
data = JSON.parse(data);
|
||||
inspectedElementHidden = data.inspectedElementHidden;
|
||||
inspectedElementHorizontalFraction =
|
||||
data.inspectedElementHorizontalFraction;
|
||||
inspectedElementVerticalFraction = data.inspectedElementVerticalFraction;
|
||||
treeListHidden = data.treeListHidden;
|
||||
treeListHorizontalFraction = data.treeListHorizontalFraction;
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
return {
|
||||
inspectedElementHidden,
|
||||
inspectedElementHorizontalFraction,
|
||||
inspectedElementVerticalFraction,
|
||||
treeListHidden,
|
||||
treeListHorizontalFraction,
|
||||
};
|
||||
}
|
||||
|
||||
function getTreeOrientation(
|
||||
wrapperElement: null | HTMLElement,
|
||||
): null | Orientation {
|
||||
if (wrapperElement != null) {
|
||||
const {width} = wrapperElement.getBoundingClientRect();
|
||||
return width > VERTICAL_TREE_MODE_MAX_WIDTH ? 'horizontal' : 'vertical';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function setResizeCSSVariable(
|
||||
resizeElement: null | HTMLElement,
|
||||
name: 'tree' | 'tree-list',
|
||||
orientation: null | Orientation,
|
||||
percentage: number,
|
||||
): void {
|
||||
if (resizeElement !== null && orientation !== null) {
|
||||
resizeElement.style.setProperty(
|
||||
`--${orientation}-resize-${name}-percentage`,
|
||||
`${percentage}%`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default (portaledContent(SuspenseTab): React$ComponentType<{}>);
|
||||
|
||||
Reference in New Issue
Block a user