mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[DevTools] Add options for disabling some features (#22136)
This commit is contained in:
+6
-1
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+30
-16
@@ -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 && (
|
||||
|
||||
+5
-1
@@ -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;
|
||||
|
||||
|
||||
Vendored
+5
-1
@@ -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
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user