Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function setReactSelectionFromBrowser(bridge) {
return;
}

// Remember to sync the selection next time we show Components tab.
// Remember to sync the selection next time we show inspected element
bridge.send('syncSelectionFromBuiltinElementsPanel');
}
},
Expand Down
44 changes: 44 additions & 0 deletions packages/react-devtools-extensions/src/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ function createBridgeAndStore() {
bridge,
browserTheme: getBrowserTheme(),
componentsPortalContainer,
inspectedElementPortalContainer,
profilerPortalContainer,
editorPortalContainer,
currentSelectedSource,
Expand Down Expand Up @@ -277,6 +278,46 @@ function createComponentsPanel() {
);
}

function createElementsInspectPanel() {
if (inspectedElementPortalContainer) {
// Panel is created and user opened it at least once
ensureInitialHTMLIsCleared(inspectedElementPortalContainer);
render();

return;
}

if (inspectedElementPane) {
// Panel is created, but wasn't opened yet, so no document is present for it
return;
}

const elementsPanel = chrome.devtools.panels.elements;
if (!elementsPanel || !elementsPanel.createSidebarPane) {
// TODO: Does Firefox support elements panel extensions?
return;
}

elementsPanel.createSidebarPane('React Element ⚛', createdPane => {
inspectedElementPane = createdPane;

createdPane.setPage('panel.html');
createdPane.setHeight('75px');

createdPane.onShown.addListener(portal => {
inspectedElementPortalContainer = portal.container;
if (inspectedElementPortalContainer != null && render) {
ensureInitialHTMLIsCleared(inspectedElementPortalContainer);

render();
portal.injectStyles(cloneStyleTags);

logEvent({event_name: 'selected-inspected-element-pane'});
}
});
});
}

function createProfilerPanel() {
if (profilerPortalContainer) {
// Panel is created and user opened it at least once
Expand Down Expand Up @@ -507,6 +548,7 @@ function mountReactDevTools() {
createComponentsPanel();
createProfilerPanel();
createSourcesEditorPanel();
createElementsInspectPanel();
// Suspense Tab is created via the hook
// TODO(enableSuspenseTab): Create eagerly once Suspense tab is stable
}
Expand Down Expand Up @@ -555,10 +597,12 @@ let componentsPanel = null;
let profilerPanel = null;
let suspensePanel = null;
let editorPane = null;
let inspectedElementPane = null;
let componentsPortalContainer = null;
let profilerPortalContainer = null;
let suspensePortalContainer = null;
let editorPortalContainer = null;
let inspectedElementPortalContainer = null;

let mostRecentOverrideTab = null;
let render = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,11 @@
padding: 0.25rem;
color: var(--color-console-error-icon);
}

.VRule {
height: 20px;
width: 1px;
flex: 0 0 1px;
margin: 0 0.5rem;
background-color: var(--color-border);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ import useEditorURL from '../useEditorURL';
import styles from './InspectedElement.css';
import Tooltip from './reach-ui/tooltip';

export type Props = {};
export type Props = {
actionButtons?: React.Node,
};

// TODO Make edits and deletes also use transition API!

const noSourcePromise = Promise.resolve(null);

export default function InspectedElementWrapper(_: Props): React.Node {
export default function InspectedElementWrapper({
actionButtons,
}: Props): React.Node {
const {inspectedElementID} = useContext(TreeStateContext);
const bridge = useContext(BridgeContext);
const store = useContext(StoreContext);
Expand Down Expand Up @@ -305,6 +309,13 @@ export default function InspectedElementWrapper(_: Props): React.Node {
symbolicatedSourcePromise={symbolicatedSourcePromise}
/>
)}

{actionButtons && (
<>
<div className={styles.VRule} />
{actionButtons}
</>
)}
</div>

{inspectedElement === null && (
Expand Down
11 changes: 11 additions & 0 deletions packages/react-devtools-shared/src/devtools/views/DevTools.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import Profiler from './Profiler/Profiler';
import SuspenseTab from './SuspenseTab/SuspenseTab';
import TabBar from './TabBar';
import EditorPane from './Editor/EditorPane';
import InspectedElementPane from './InspectedElement/InspectedElementPane';
import {SettingsContextController} from './Settings/SettingsContext';
import {TreeContextController} from './Components/TreeContext';
import ViewElementSourceContext from './Components/ViewElementSourceContext';
Expand Down Expand Up @@ -100,6 +101,7 @@ export type Props = {
// The root <DevTools> app is rendered in the top-level extension window,
// but individual tabs (e.g. Components, Profiling) can be rendered into portals within their browser panels.
componentsPortalContainer?: Element,
inspectedElementPortalContainer?: Element,
profilerPortalContainer?: Element,
suspensePortalContainer?: Element,
editorPortalContainer?: Element,
Expand Down Expand Up @@ -155,6 +157,7 @@ export default function DevTools({
canViewElementSourceFunction,
componentsPortalContainer,
editorPortalContainer,
inspectedElementPortalContainer,
profilerPortalContainer,
suspensePortalContainer,
currentSelectedSource,
Expand Down Expand Up @@ -379,6 +382,14 @@ export default function DevTools({
portalContainer={editorPortalContainer}
/>
) : null}
{inspectedElementPortalContainer ? (
<InspectedElementPane
selectedSource={currentSelectedSource}
portalContainer={
inspectedElementPortalContainer
}
/>
) : null}
</ThemeProvider>
</SuspenseTreeContextController>
</InspectedElementContextController>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.InspectedElementPane, .InspectedElementPane * {
box-sizing: border-box;
-webkit-font-smoothing: var(--font-smoothing);
}

.InspectedElementPane {
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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* 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 portaledContent from 'react-devtools-shared/src/devtools/views/portaledContent';
import {OptionsContext} from 'react-devtools-shared/src/devtools/views/context';
import InspectedElement from 'react-devtools-shared/src/devtools/views/Components/InspectedElement';
import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal';
import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle';
import {SettingsModalContextController} from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContext';
import styles from './InspectedElementPane.css';

export type Props = {};

function InspectedElementPane({}: Props) {
const {hideSettings} = useContext(OptionsContext);
return (
<SettingsModalContextController>
<div className={styles.InspectedElementPane}>
<InspectedElement
actionButtons={!hideSettings && <SettingsModalContextToggle />}
/>
<SettingsModal />
</div>
</SettingsModalContextController>
);
}
export default (portaledContent(InspectedElementPane): component());