[DevTools] Add options for disabling some features (#22136)

This commit is contained in:
Holger Benl
2021-08-20 12:55:50 -04:00
committed by GitHub
parent e1d53e4d33
commit 482d848170
8 changed files with 184 additions and 90 deletions
+6 -1
View File
@@ -20,11 +20,16 @@ import type {Wall} from 'react-devtools-shared/src/types';
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
import type {Props} from 'react-devtools-shared/src/devtools/views/DevTools';
export function createStore(bridge: FrontendBridge): Store {
type Config = {|
supportsNativeInspection?: boolean,
|};
export function createStore(bridge: FrontendBridge, config?: Config): Store {
return new Store(bridge, {
checkBridgeProtocolCompatibility: true,
supportsTraceUpdates: true,
supportsSchedulingProfiler: true,
supportsNativeInspection: config?.supportsNativeInspection !== false,
});
}
@@ -10,7 +10,7 @@
import * as React from 'react';
import {useCallback, useContext} from 'react';
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
import {BridgeContext, StoreContext} from '../context';
import {BridgeContext, StoreContext, OptionsContext} from '../context';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';
import {ModalDialogContext} from '../ModalDialog';
@@ -37,6 +37,12 @@ export default function InspectedElementWrapper(_: Props) {
);
const bridge = useContext(BridgeContext);
const store = useContext(StoreContext);
const {
hideToggleErrorAction,
hideToggleSuspenseAction,
hideLogAction,
hideViewSourceAction,
} = useContext(OptionsContext);
const {dispatch: modalDialogDispatch} = useContext(ModalDialogContext);
const {
@@ -108,10 +114,14 @@ export default function InspectedElementWrapper(_: Props) {
inspectedElement.state != null;
const canToggleError =
inspectedElement != null && inspectedElement.canToggleError;
!hideToggleErrorAction &&
inspectedElement != null &&
inspectedElement.canToggleError;
const canToggleSuspense =
inspectedElement != null && inspectedElement.canToggleSuspense;
!hideToggleSuspenseAction &&
inspectedElement != null &&
inspectedElement.canToggleSuspense;
const toggleErrored = useCallback(() => {
if (inspectedElement == null || targetErrorBoundaryID == null) {
@@ -248,19 +258,23 @@ export default function InspectedElementWrapper(_: Props) {
<ButtonIcon type="view-dom" />
</Button>
)}
<Button
className={styles.IconButton}
onClick={logElement}
title="Log this component data to the console">
<ButtonIcon type="log-data" />
</Button>
<Button
className={styles.IconButton}
disabled={!canViewSource}
onClick={viewSource}
title="View source for this element">
<ButtonIcon type="view-source" />
</Button>
{!hideLogAction && (
<Button
className={styles.IconButton}
onClick={logElement}
title="Log this component data to the console">
<ButtonIcon type="log-data" />
</Button>
)}
{!hideViewSourceAction && (
<Button
className={styles.IconButton}
disabled={!canViewSource}
onClick={viewSource}
title="View source for this element">
<ButtonIcon type="view-source" />
</Button>
)}
</div>
{inspectedElement === null && (
@@ -9,6 +9,7 @@
import {copy} from 'clipboard-js';
import * as React from 'react';
import {OptionsContext} from '../context';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';
import KeyValue from './KeyValue';
@@ -35,6 +36,8 @@ export default function InspectedElementPropsTree({
inspectedElement,
store,
}: Props) {
const {readOnly} = React.useContext(OptionsContext);
const {
canEditFunctionProps,
canEditFunctionPropsDeletePaths,
@@ -45,7 +48,8 @@ export default function InspectedElementPropsTree({
const canDeletePaths =
type === ElementTypeClass || canEditFunctionPropsDeletePaths;
const canEditValues = type === ElementTypeClass || canEditFunctionProps;
const canEditValues =
!readOnly && (type === ElementTypeClass || canEditFunctionProps);
const canRenamePaths =
type === ElementTypeClass || canEditFunctionPropsRenamePaths;
@@ -8,6 +8,7 @@
*/
import * as React from 'react';
import {OptionsContext} from '../context';
import EditableValue from './EditableValue';
import Store from '../../store';
import {ElementTypeSuspense} from 'react-devtools-shared/src/types';
@@ -27,7 +28,10 @@ export default function InspectedElementSuspenseToggle({
inspectedElement,
store,
}: Props) {
const {canToggleSuspense, id, state, type} = inspectedElement;
const {readOnly} = React.useContext(OptionsContext);
const {id, state, type} = inspectedElement;
const canToggleSuspense = !readOnly && inspectedElement.canToggleSuspense;
if (type !== ElementTypeSuspense) {
return null;
@@ -9,6 +9,7 @@
import * as React from 'react';
import {useTransition, useContext, useRef, useState} from 'react';
import {OptionsContext} from '../context';
import EditableName from './EditableName';
import EditableValue from './EditableValue';
import NewArrayValue from './NewArrayValue';
@@ -74,6 +75,11 @@ export default function KeyValue({
store,
value,
}: KeyValueProps) {
const {readOnly: readOnlyGlobalFlag} = useContext(OptionsContext);
canDeletePaths = !readOnlyGlobalFlag && canDeletePaths;
canEditValues = !readOnlyGlobalFlag && canEditValues;
canRenamePaths = !readOnlyGlobalFlag && canRenamePaths;
const {id} = inspectedElement;
const [isOpen, setIsOpen] = useState<boolean>(false);
@@ -82,10 +88,10 @@ export default function KeyValue({
const {inspectPaths} = useContext(InspectedElementContext);
let isInspectable = false;
let isReadOnly = false;
let isReadOnlyBasedOnMetadata = false;
if (value !== null && typeof value === 'object') {
isInspectable = value[meta.inspectable] && value[meta.size] !== 0;
isReadOnly = value[meta.readonly];
isReadOnlyBasedOnMetadata = value[meta.readonly];
}
const [isInspectPathsPending, startInspectPathsTransition] = useTransition();
@@ -330,9 +336,9 @@ export default function KeyValue({
key={index}
alphaSort={alphaSort}
bridge={bridge}
canDeletePaths={canDeletePaths && !isReadOnly}
canEditValues={canEditValues && !isReadOnly}
canRenamePaths={canRenamePaths && !isReadOnly}
canDeletePaths={canDeletePaths && !isReadOnlyBasedOnMetadata}
canEditValues={canEditValues && !isReadOnlyBasedOnMetadata}
canRenamePaths={canRenamePaths && !isReadOnlyBasedOnMetadata}
canRenamePathsAtDepth={canRenamePathsAtDepth}
depth={depth + 1}
element={element}
@@ -348,7 +354,7 @@ export default function KeyValue({
/>
));
if (canEditValues && !isReadOnly) {
if (canEditValues && !isReadOnlyBasedOnMetadata) {
children.push(
<NewArrayValue
key="NewKeyValue"
@@ -404,9 +410,9 @@ export default function KeyValue({
key={key}
alphaSort={alphaSort}
bridge={bridge}
canDeletePaths={canDeletePaths && !isReadOnly}
canEditValues={canEditValues && !isReadOnly}
canRenamePaths={canRenamePaths && !isReadOnly}
canDeletePaths={canDeletePaths && !isReadOnlyBasedOnMetadata}
canEditValues={canEditValues && !isReadOnlyBasedOnMetadata}
canRenamePaths={canRenamePaths && !isReadOnlyBasedOnMetadata}
canRenamePathsAtDepth={canRenamePathsAtDepth}
depth={depth + 1}
element={element}
@@ -421,7 +427,7 @@ export default function KeyValue({
/>
));
if (canEditValues && !isReadOnly) {
if (canEditValues && !isReadOnlyBasedOnMetadata) {
children.push(
<NewKeyValue
key="NewKeyValue"
@@ -23,7 +23,7 @@ import {FixedSizeList} from 'react-window';
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
import Icon from '../Icon';
import {SettingsContext} from '../Settings/SettingsContext';
import {BridgeContext, StoreContext} from '../context';
import {BridgeContext, StoreContext, OptionsContext} from '../context';
import Element from './Element';
import InspectHostNodesToggle from './InspectHostNodesToggle';
import OwnersStack from './OwnersStack';
@@ -62,6 +62,7 @@ export default function Tree(props: Props) {
} = useContext(TreeStateContext);
const bridge = useContext(BridgeContext);
const store = useContext(StoreContext);
const {hideSettings} = useContext(OptionsContext);
const [isNavigatingWithKeyboard, setIsNavigatingWithKeyboard] = useState(
false,
);
@@ -344,11 +345,11 @@ export default function Tree(props: Props) {
<Suspense fallback={<Loading />}>
{ownerID !== null ? <OwnersStack /> : <SearchInput />}
</Suspense>
<div className={styles.VRule} />
{showInlineWarningsAndErrors &&
ownerID === null &&
(errors > 0 || warnings > 0) && (
<React.Fragment>
<div className={styles.VRule} />
{errors > 0 && (
<div className={styles.IconAndCount}>
<Icon className={styles.ErrorIcon} type="error" />
@@ -376,10 +377,14 @@ export default function Tree(props: Props) {
title="Clear all errors and warnings">
<ButtonIcon type="clear" />
</Button>
<div className={styles.VRule} />
</React.Fragment>
)}
<SettingsModalContextToggle />
{!hideSettings && (
<Fragment>
<div className={styles.VRule} />
<SettingsModalContextToggle />
</Fragment>
)}
</div>
<div
className={styles.AutoSizerWrapper}
+95 -57
View File
@@ -15,7 +15,12 @@ import '@reach/tooltip/styles.css';
import * as React from 'react';
import {useEffect, useLayoutEffect, useMemo, useRef} from 'react';
import Store from '../store';
import {BridgeContext, ContextMenuContext, StoreContext} from './context';
import {
BridgeContext,
ContextMenuContext,
StoreContext,
OptionsContext,
} from './context';
import Components from './Components/Components';
import Profiler from './Profiler/Profiler';
import TabBar from './TabBar';
@@ -74,6 +79,12 @@ export type Props = {|
warnIfUnsupportedVersionDetected?: boolean,
viewAttributeSourceFunction?: ?ViewAttributeSource,
viewElementSourceFunction?: ?ViewElementSource,
readOnly?: boolean,
hideSettings?: boolean,
hideToggleErrorAction?: boolean,
hideToggleSuspenseAction?: boolean,
hideLogAction?: boolean,
hideViewSourceAction?: boolean,
// This property is used only by the web extension target.
// The built-in tab UI is hidden in that case, in favor of the browser's own panel tabs.
@@ -126,6 +137,12 @@ export default function DevTools({
warnIfUnsupportedVersionDetected = false,
viewAttributeSourceFunction,
viewElementSourceFunction,
readOnly,
hideSettings,
hideToggleErrorAction,
hideToggleSuspenseAction,
hideLogAction,
hideViewSourceAction,
}: Props) {
const [currentTab, setTab] = useLocalStorage<TabID>(
'React::DevTools::defaultTab',
@@ -138,6 +155,25 @@ export default function DevTools({
tab = overrideTab;
}
const options = useMemo(
() => ({
readOnly: readOnly || false,
hideSettings: hideSettings || false,
hideToggleErrorAction: hideToggleErrorAction || false,
hideToggleSuspenseAction: hideToggleSuspenseAction || false,
hideLogAction: hideLogAction || false,
hideViewSourceAction: hideViewSourceAction || false,
}),
[
readOnly,
hideSettings,
hideToggleErrorAction,
hideToggleSuspenseAction,
hideLogAction,
hideViewSourceAction,
],
);
const viewElementSource = useMemo(
() => ({
canViewElementSourceFunction: canViewElementSourceFunction || null,
@@ -210,65 +246,67 @@ export default function DevTools({
return (
<BridgeContext.Provider value={bridge}>
<StoreContext.Provider value={store}>
<ContextMenuContext.Provider value={contextMenu}>
<ModalDialogContextController>
<SettingsContextController
browserTheme={browserTheme}
componentsPortalContainer={componentsPortalContainer}
profilerPortalContainer={profilerPortalContainer}>
<ViewElementSourceContext.Provider value={viewElementSource}>
<HookNamesContext.Provider value={hookNamesContext}>
<TreeContextController>
<ProfilerContextController>
<SchedulingProfilerContextController>
<ThemeProvider>
<div
className={styles.DevTools}
ref={devToolsRef}
data-react-devtools-portal-root={true}>
{showTabBar && (
<div className={styles.TabBar}>
<ReactLogo />
<span className={styles.DevToolsVersion}>
{process.env.DEVTOOLS_VERSION}
</span>
<div className={styles.Spacer} />
<TabBar
currentTab={tab}
id="DevTools"
selectTab={setTab}
tabs={tabs}
type="navigation"
<OptionsContext.Provider value={options}>
<ContextMenuContext.Provider value={contextMenu}>
<ModalDialogContextController>
<SettingsContextController
browserTheme={browserTheme}
componentsPortalContainer={componentsPortalContainer}
profilerPortalContainer={profilerPortalContainer}>
<ViewElementSourceContext.Provider value={viewElementSource}>
<HookNamesContext.Provider value={hookNamesContext}>
<TreeContextController>
<ProfilerContextController>
<SchedulingProfilerContextController>
<ThemeProvider>
<div
className={styles.DevTools}
ref={devToolsRef}
data-react-devtools-portal-root={true}>
{showTabBar && (
<div className={styles.TabBar}>
<ReactLogo />
<span className={styles.DevToolsVersion}>
{process.env.DEVTOOLS_VERSION}
</span>
<div className={styles.Spacer} />
<TabBar
currentTab={tab}
id="DevTools"
selectTab={setTab}
tabs={tabs}
type="navigation"
/>
</div>
)}
<div
className={styles.TabContent}
hidden={tab !== 'components'}>
<Components
portalContainer={componentsPortalContainer}
/>
</div>
<div
className={styles.TabContent}
hidden={tab !== 'profiler'}>
<Profiler
portalContainer={profilerPortalContainer}
/>
</div>
)}
<div
className={styles.TabContent}
hidden={tab !== 'components'}>
<Components
portalContainer={componentsPortalContainer}
/>
</div>
<div
className={styles.TabContent}
hidden={tab !== 'profiler'}>
<Profiler
portalContainer={profilerPortalContainer}
/>
</div>
</div>
</ThemeProvider>
</SchedulingProfilerContextController>
</ProfilerContextController>
</TreeContextController>
</HookNamesContext.Provider>
</ViewElementSourceContext.Provider>
</SettingsContextController>
<UnsupportedBridgeProtocolDialog />
{warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
{warnIfUnsupportedVersionDetected && <UnsupportedVersionDialog />}
</ModalDialogContextController>
</ContextMenuContext.Provider>
</ThemeProvider>
</SchedulingProfilerContextController>
</ProfilerContextController>
</TreeContextController>
</HookNamesContext.Provider>
</ViewElementSourceContext.Provider>
</SettingsContextController>
<UnsupportedBridgeProtocolDialog />
{warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
{warnIfUnsupportedVersionDetected && <UnsupportedVersionDialog />}
</ModalDialogContextController>
</ContextMenuContext.Provider>
</OptionsContext.Provider>
</StoreContext.Provider>
</BridgeContext.Provider>
);
@@ -31,3 +31,21 @@ export const ContextMenuContext = createContext<ContextMenuContextType>({
viewAttributeSourceFunction: null,
});
ContextMenuContext.displayName = 'ContextMenuContext';
export type OptionsContextType = {|
readOnly: boolean,
hideSettings: boolean,
hideToggleErrorAction: boolean,
hideToggleSuspenseAction: boolean,
hideLogAction: boolean,
hideViewSourceAction: boolean,
|};
export const OptionsContext = createContext<OptionsContextType>({
readOnly: false,
hideSettings: false,
hideToggleErrorAction: false,
hideToggleSuspenseAction: false,
hideLogAction: false,
hideViewSourceAction: false,
});