mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
d14ce51327
## Summary - While rolling out RDT 5.2.0 on Fusebox, we've discovered that context menus don't work well with this environment. The reason for it is the context menu state implementation - in a global context we define a map of registered context menus, basically what is shown at the moment (see deleted Contexts.js file). These maps are not invalidated on each re-initialization of DevTools frontend, since the bundle (react-devtools-fusebox module) is not reloaded, and this results into RDT throwing an error that some context menu was already registered. - We should not keep such data in a global state, since there is no guarantee that this will be invalidated with each re-initialization of DevTools (like with browser extension, for example). - The new implementation is based on a `ContextMenuContainer` component, which will add all required `contextmenu` event listeners to the anchor-element. This component will also receive a list of `items` that will be displayed in the shown context menu. - The `ContextMenuContainer` component is also using `useImperativeHandle` hook to extend the instance of the component, so context menus can be managed imperatively via `ref`: `contextMenu.current?.hide()`, for example. - **Changed**: The option for copying value to clipboard is now hidden for functions. The reasons for it are: - It is broken in the current implementation, because we call `JSON.stringify` on the value, see `packages/react-devtools-shared/src/backend/utils.js`. - I don't see any reasonable value in doing this for the user, since `Go to definition` option is available and you can inspect the real code and then copy it. - We already filter out fields from objects, if their value is a function, because the whole object is passed to `JSON.stringify`. ## How did you test this change? ### Works with element props and hooks: - All context menu items work reliably for props items - All context menu items work reliably or hooks items https://github.com/facebook/react/assets/28902667/5e2d58b0-92fa-4624-ad1e-2bbd7f12678f ### Works with timeline profiler: - All context menu items work reliably: copying, zooming, ... - Context menu automatically closes on the scroll event https://github.com/facebook/react/assets/28902667/de744cd0-372a-402a-9fa0-743857048d24 ### Works with Fusebox: - Produces no errors - Copy to clipboard context menu item works reliably https://github.com/facebook/react/assets/28902667/0288f5bf-0d44-435c-8842-6b57bc8a7a24
136 lines
3.3 KiB
JavaScript
136 lines
3.3 KiB
JavaScript
/**
|
|
* 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
|
|
*/
|
|
|
|
import * as React from 'react';
|
|
import {useContext} from 'react';
|
|
|
|
import {ContextMenuContext} from '../context';
|
|
import {
|
|
copyInspectedElementPath as copyInspectedElementPathAPI,
|
|
storeAsGlobal as storeAsGlobalAPI,
|
|
} from '../../../backendAPI';
|
|
import Icon from '../Icon';
|
|
import ContextMenuContainer from '../../ContextMenu/ContextMenuContainer';
|
|
|
|
import type Store from 'react-devtools-shared/src/devtools/store';
|
|
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
|
import type {ContextMenuContextType} from '../context';
|
|
|
|
import styles from './KeyValueContextMenuContainer.css';
|
|
|
|
type Props = {
|
|
children: React.Node,
|
|
anchorElementRef: {
|
|
current: React.ElementRef<any> | null,
|
|
},
|
|
store: Store,
|
|
attributeSourceCanBeInspected: boolean,
|
|
bridge: FrontendBridge,
|
|
id: number,
|
|
path: Array<any>,
|
|
canBeCopiedToClipboard: boolean,
|
|
};
|
|
|
|
export default function KeyValueContextMenuContainer({
|
|
children,
|
|
anchorElementRef,
|
|
store,
|
|
attributeSourceCanBeInspected,
|
|
bridge,
|
|
id,
|
|
path,
|
|
canBeCopiedToClipboard,
|
|
}: Props): React.Node {
|
|
const {
|
|
isEnabledForInspectedElement: isContextMenuEnabledForInspectedElement,
|
|
viewAttributeSourceFunction,
|
|
} = useContext<ContextMenuContextType>(ContextMenuContext);
|
|
|
|
const menuItems = React.useMemo(() => {
|
|
const items = [
|
|
{
|
|
onClick: () => {
|
|
const rendererID = store.getRendererIDForElement(id);
|
|
if (rendererID !== null) {
|
|
storeAsGlobalAPI({
|
|
bridge,
|
|
id,
|
|
path,
|
|
rendererID,
|
|
});
|
|
}
|
|
},
|
|
content: (
|
|
<span className={styles.ContextMenuItemContent}>
|
|
<Icon type="store-as-global-variable" />
|
|
<label>Store as global variable</label>
|
|
</span>
|
|
),
|
|
},
|
|
];
|
|
|
|
if (canBeCopiedToClipboard) {
|
|
items.unshift({
|
|
onClick: () => {
|
|
const rendererID = store.getRendererIDForElement(id);
|
|
if (rendererID !== null) {
|
|
copyInspectedElementPathAPI({
|
|
bridge,
|
|
id,
|
|
path,
|
|
rendererID,
|
|
});
|
|
}
|
|
},
|
|
content: (
|
|
<span className={styles.ContextMenuItemContent}>
|
|
<Icon type="copy" />
|
|
<label>Copy value to clipboard</label>
|
|
</span>
|
|
),
|
|
});
|
|
}
|
|
|
|
if (viewAttributeSourceFunction != null && attributeSourceCanBeInspected) {
|
|
items.push({
|
|
onClick: () => viewAttributeSourceFunction(id, path),
|
|
content: (
|
|
<span className={styles.ContextMenuItemContent}>
|
|
<Icon type="code" />
|
|
<label>Go to definition</label>
|
|
</span>
|
|
),
|
|
});
|
|
}
|
|
return items;
|
|
}, [
|
|
store,
|
|
viewAttributeSourceFunction,
|
|
attributeSourceCanBeInspected,
|
|
canBeCopiedToClipboard,
|
|
bridge,
|
|
id,
|
|
path,
|
|
]);
|
|
|
|
if (!isContextMenuEnabledForInspectedElement) {
|
|
return children;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{children}
|
|
<ContextMenuContainer
|
|
anchorElementRef={anchorElementRef}
|
|
items={menuItems}
|
|
/>
|
|
</>
|
|
);
|
|
}
|