2019-08-28 01:54:01 +08:00
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
2019-02-13 09:41:39 +08:00
|
|
|
|
2019-04-21 05:20:48 +08:00
|
|
|
// Reach styles need to come before any component styles.
|
2019-09-06 22:47:44 +08:00
|
|
|
// This makes overriding the styles simpler.
|
2019-04-21 05:20:48 +08:00
|
|
|
import '@reach/menu-button/styles.css';
|
2019-04-23 23:41:52 +08:00
|
|
|
import '@reach/tooltip/styles.css';
|
2019-04-21 05:20:48 +08:00
|
|
|
|
2020-02-22 11:45:20 +08:00
|
|
|
import * as React from 'react';
|
2020-03-19 02:05:41 +08:00
|
|
|
import {useEffect, useMemo, useRef} from 'react';
|
2019-02-13 09:41:39 +08:00
|
|
|
import Store from '../store';
|
2019-12-19 04:12:34 +08:00
|
|
|
import {BridgeContext, ContextMenuContext, StoreContext} from './context';
|
2019-04-03 02:53:46 +08:00
|
|
|
import Components from './Components/Components';
|
2019-03-07 06:01:52 +08:00
|
|
|
import Profiler from './Profiler/Profiler';
|
2019-02-13 09:41:39 +08:00
|
|
|
import TabBar from './TabBar';
|
2019-08-14 08:58:03 +08:00
|
|
|
import {SettingsContextController} from './Settings/SettingsContext';
|
|
|
|
|
import {TreeContextController} from './Components/TreeContext';
|
2019-04-23 01:19:59 +08:00
|
|
|
import ViewElementSourceContext from './Components/ViewElementSourceContext';
|
2019-08-14 08:58:03 +08:00
|
|
|
import {ProfilerContextController} from './Profiler/ProfilerContext';
|
|
|
|
|
import {ModalDialogContextController} from './ModalDialog';
|
2019-03-11 00:01:05 +08:00
|
|
|
import ReactLogo from './ReactLogo';
|
2019-09-26 23:41:46 +08:00
|
|
|
import UnsupportedVersionDialog from './UnsupportedVersionDialog';
|
2019-07-19 07:24:22 +08:00
|
|
|
import WarnIfLegacyBackendDetected from './WarnIfLegacyBackendDetected';
|
2020-03-19 02:05:41 +08:00
|
|
|
import {useLocalStorage} from './hooks';
|
2019-02-13 09:41:39 +08:00
|
|
|
|
2019-02-13 23:59:49 +08:00
|
|
|
import styles from './DevTools.css';
|
|
|
|
|
|
2019-02-13 09:41:39 +08:00
|
|
|
import './root.css';
|
|
|
|
|
|
2019-08-14 08:58:03 +08:00
|
|
|
import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types';
|
|
|
|
|
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
2019-07-14 01:05:04 +08:00
|
|
|
|
2019-02-13 09:41:39 +08:00
|
|
|
export type BrowserTheme = 'dark' | 'light';
|
2019-08-03 00:27:13 +08:00
|
|
|
export type TabID = 'components' | 'profiler';
|
2019-07-14 01:05:04 +08:00
|
|
|
export type ViewElementSource = (
|
|
|
|
|
id: number,
|
2019-08-14 08:58:03 +08:00
|
|
|
inspectedElement: InspectedElement,
|
2019-07-14 01:05:04 +08:00
|
|
|
) => void;
|
2019-12-19 04:12:34 +08:00
|
|
|
export type ViewAttributeSource = (
|
|
|
|
|
id: number,
|
|
|
|
|
path: Array<string | number>,
|
|
|
|
|
) => void;
|
2019-07-26 00:28:45 +08:00
|
|
|
export type CanViewElementSource = (
|
2019-08-14 08:58:03 +08:00
|
|
|
inspectedElement: InspectedElement,
|
2019-07-26 00:28:45 +08:00
|
|
|
) => boolean;
|
2019-02-13 09:41:39 +08:00
|
|
|
|
|
|
|
|
export type Props = {|
|
2019-07-21 05:08:23 +08:00
|
|
|
bridge: FrontendBridge,
|
2019-07-14 01:05:04 +08:00
|
|
|
browserTheme?: BrowserTheme,
|
2019-07-26 00:28:45 +08:00
|
|
|
canViewElementSourceFunction?: ?CanViewElementSource,
|
2019-03-19 00:11:28 +08:00
|
|
|
defaultTab?: TabID,
|
2019-12-19 04:12:34 +08:00
|
|
|
enabledInspectedElementContextMenu?: boolean,
|
2019-02-13 09:41:39 +08:00
|
|
|
showTabBar?: boolean,
|
2019-02-15 03:45:11 +08:00
|
|
|
store: Store,
|
2019-07-19 07:24:22 +08:00
|
|
|
warnIfLegacyBackendDetected?: boolean,
|
2019-09-26 23:41:46 +08:00
|
|
|
warnIfUnsupportedVersionDetected?: boolean,
|
2019-12-19 04:12:34 +08:00
|
|
|
viewAttributeSourceFunction?: ?ViewAttributeSource,
|
2019-07-14 01:05:04 +08:00
|
|
|
viewElementSourceFunction?: ?ViewElementSource,
|
2019-03-19 00:57:08 +08:00
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
// This is done to save space within the app.
|
|
|
|
|
// Because of this, the extension needs to be able to change which tab is active/rendered.
|
|
|
|
|
overrideTab?: TabID,
|
|
|
|
|
|
|
|
|
|
// To avoid potential multi-root trickiness, the web extension uses portals to render tabs.
|
|
|
|
|
// The root <DevTools> app is rendered in the top-level extension window,
|
2019-04-03 02:53:46 +08:00
|
|
|
// but individual tabs (e.g. Components, Profiling) can be rendered into portals within their browser panels.
|
|
|
|
|
componentsPortalContainer?: Element,
|
2019-03-19 00:57:08 +08:00
|
|
|
profilerPortalContainer?: Element,
|
2019-02-13 09:41:39 +08:00
|
|
|
|};
|
|
|
|
|
|
2019-04-03 02:53:46 +08:00
|
|
|
const componentsTab = {
|
|
|
|
|
id: ('components': TabID),
|
|
|
|
|
icon: 'components',
|
2019-04-03 02:46:55 +08:00
|
|
|
label: 'Components',
|
|
|
|
|
title: 'React Components',
|
2019-03-11 00:01:05 +08:00
|
|
|
};
|
|
|
|
|
const profilerTab = {
|
|
|
|
|
id: ('profiler': TabID),
|
|
|
|
|
icon: 'profiler',
|
|
|
|
|
label: 'Profiler',
|
|
|
|
|
title: 'React Profiler',
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-11 05:57:26 +08:00
|
|
|
const tabs = [componentsTab, profilerTab];
|
2019-03-11 00:01:05 +08:00
|
|
|
|
2019-02-13 09:41:39 +08:00
|
|
|
export default function DevTools({
|
|
|
|
|
bridge,
|
|
|
|
|
browserTheme = 'light',
|
2019-08-14 06:59:43 +08:00
|
|
|
canViewElementSourceFunction,
|
2019-04-03 02:53:46 +08:00
|
|
|
componentsPortalContainer,
|
2019-12-19 04:12:34 +08:00
|
|
|
defaultTab = 'components',
|
|
|
|
|
enabledInspectedElementContextMenu = false,
|
2019-03-18 04:52:37 +08:00
|
|
|
overrideTab,
|
2019-03-19 00:11:28 +08:00
|
|
|
profilerPortalContainer,
|
2019-02-13 09:41:39 +08:00
|
|
|
showTabBar = false,
|
2019-02-15 03:45:11 +08:00
|
|
|
store,
|
2019-07-19 07:24:22 +08:00
|
|
|
warnIfLegacyBackendDetected = false,
|
2019-09-26 23:41:46 +08:00
|
|
|
warnIfUnsupportedVersionDetected = false,
|
2019-12-19 04:12:34 +08:00
|
|
|
viewAttributeSourceFunction,
|
2019-08-14 06:59:43 +08:00
|
|
|
viewElementSourceFunction,
|
2019-02-13 09:41:39 +08:00
|
|
|
}: Props) {
|
2020-04-02 03:35:52 +08:00
|
|
|
const [currentTab, setTab] = useLocalStorage<TabID>(
|
2020-03-19 02:05:41 +08:00
|
|
|
'React::DevTools::defaultTab',
|
|
|
|
|
defaultTab,
|
|
|
|
|
);
|
|
|
|
|
|
2020-04-02 03:35:52 +08:00
|
|
|
let tab = currentTab;
|
|
|
|
|
|
2020-03-19 02:05:41 +08:00
|
|
|
if (overrideTab != null) {
|
|
|
|
|
tab = overrideTab;
|
2019-03-18 04:52:37 +08:00
|
|
|
}
|
|
|
|
|
|
2019-07-14 01:05:04 +08:00
|
|
|
const viewElementSource = useMemo(
|
|
|
|
|
() => ({
|
2019-08-15 00:24:35 +08:00
|
|
|
canViewElementSourceFunction: canViewElementSourceFunction || null,
|
|
|
|
|
viewElementSourceFunction: viewElementSourceFunction || null,
|
2019-07-14 01:05:04 +08:00
|
|
|
}),
|
2019-08-14 08:58:03 +08:00
|
|
|
[canViewElementSourceFunction, viewElementSourceFunction],
|
2019-07-14 01:05:04 +08:00
|
|
|
);
|
|
|
|
|
|
2019-12-19 04:12:34 +08:00
|
|
|
const contextMenu = useMemo(
|
|
|
|
|
() => ({
|
|
|
|
|
isEnabledForInspectedElement: enabledInspectedElementContextMenu,
|
|
|
|
|
viewAttributeSourceFunction: viewAttributeSourceFunction || null,
|
|
|
|
|
}),
|
2019-12-30 05:27:44 +08:00
|
|
|
[enabledInspectedElementContextMenu, viewAttributeSourceFunction],
|
2019-12-19 04:12:34 +08:00
|
|
|
);
|
|
|
|
|
|
2020-03-19 02:05:41 +08:00
|
|
|
const devToolsRef = useRef<HTMLElement | null>(null);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!showTabBar) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const div = devToolsRef.current;
|
|
|
|
|
if (div === null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ownerWindow = div.ownerDocument.defaultView;
|
|
|
|
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
|
|
|
if (event.ctrlKey || event.metaKey) {
|
|
|
|
|
switch (event.key) {
|
|
|
|
|
case '1':
|
|
|
|
|
setTab(tabs[0].id);
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
break;
|
|
|
|
|
case '2':
|
|
|
|
|
setTab(tabs[1].id);
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
ownerWindow.addEventListener('keydown', handleKeyDown);
|
|
|
|
|
return () => {
|
|
|
|
|
ownerWindow.removeEventListener('keydown', handleKeyDown);
|
|
|
|
|
};
|
|
|
|
|
}, [showTabBar]);
|
|
|
|
|
|
2020-01-09 21:54:11 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
return () => {
|
|
|
|
|
try {
|
|
|
|
|
bridge.shutdown();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Attempting to use a disconnected port.
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}, [bridge]);
|
2019-08-16 08:34:34 +08:00
|
|
|
|
2019-03-19 00:11:28 +08:00
|
|
|
return (
|
2019-02-13 09:41:39 +08:00
|
|
|
<BridgeContext.Provider value={bridge}>
|
|
|
|
|
<StoreContext.Provider value={store}>
|
2019-12-19 04:12:34 +08:00
|
|
|
<ContextMenuContext.Provider value={contextMenu}>
|
|
|
|
|
<ModalDialogContextController>
|
|
|
|
|
<SettingsContextController
|
|
|
|
|
browserTheme={browserTheme}
|
|
|
|
|
componentsPortalContainer={componentsPortalContainer}
|
|
|
|
|
profilerPortalContainer={profilerPortalContainer}>
|
|
|
|
|
<ViewElementSourceContext.Provider value={viewElementSource}>
|
|
|
|
|
<TreeContextController>
|
|
|
|
|
<ProfilerContextController>
|
2020-03-19 02:05:41 +08:00
|
|
|
<div className={styles.DevTools} ref={devToolsRef}>
|
2019-12-19 04:12:34 +08:00
|
|
|
{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}
|
2019-05-09 01:50:32 +08:00
|
|
|
/>
|
|
|
|
|
</div>
|
2019-12-19 04:12:34 +08:00
|
|
|
<div
|
|
|
|
|
className={styles.TabContent}
|
|
|
|
|
hidden={tab !== 'profiler'}>
|
|
|
|
|
<Profiler portalContainer={profilerPortalContainer} />
|
|
|
|
|
</div>
|
2019-04-23 01:19:59 +08:00
|
|
|
</div>
|
2019-12-19 04:12:34 +08:00
|
|
|
</ProfilerContextController>
|
|
|
|
|
</TreeContextController>
|
|
|
|
|
</ViewElementSourceContext.Provider>
|
|
|
|
|
</SettingsContextController>
|
|
|
|
|
{warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
|
|
|
|
|
{warnIfUnsupportedVersionDetected && <UnsupportedVersionDialog />}
|
|
|
|
|
</ModalDialogContextController>
|
|
|
|
|
</ContextMenuContext.Provider>
|
2019-02-13 09:41:39 +08:00
|
|
|
</StoreContext.Provider>
|
|
|
|
|
</BridgeContext.Provider>
|
|
|
|
|
);
|
|
|
|
|
}
|