mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Implemented select-DOM-element button
This commit is contained in:
1 parent
2fbacbff01
commit
1d95098177
9 files changed
+218
-19
No files matched your search
+56
-2
@@ -28,10 +28,21 @@ export default class Agent extends EventEmitter {
|
||||
bridge.addListener('highlightElementInDOM', this.highlightElementInDOM);
|
||||
bridge.addListener('inspectElement', this.inspectElement);
|
||||
bridge.addListener('selectElement', this.selectElement);
|
||||
bridge.addListener('startInspectingDOM', this.startInspectingDOM);
|
||||
bridge.addListener('stopInspectingDOM', this.stopInspectingDOM);
|
||||
bridge.addListener('shutdown', this.shutdown);
|
||||
}
|
||||
|
||||
// TODO Listen to bridge for things like selection.
|
||||
// bridge.addListener('...'), this...);
|
||||
getIDForNode(node: Object): number | null {
|
||||
for (let rendererID in this._rendererInterfaces) {
|
||||
// A renderer will throw if it can't find a fiber for the specified node.
|
||||
try {
|
||||
// $FlowFixMe
|
||||
const renderer = this._rendererInterfaces[rendererID];
|
||||
return renderer.getFiberIDFromNative(node, true);
|
||||
} catch (e) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
highlightElementInDOM = ({
|
||||
@@ -98,6 +109,20 @@ export default class Agent extends EventEmitter {
|
||||
this.emit('shutdown');
|
||||
};
|
||||
|
||||
startInspectingDOM = () => {
|
||||
window.addEventListener('click', this._onClick);
|
||||
window.addEventListener('mousedown', this._onMouseDown);
|
||||
window.addEventListener('mouseover', this._onMouseOver);
|
||||
};
|
||||
|
||||
stopInspectingDOM = () => {
|
||||
hideOverlay();
|
||||
|
||||
window.removeEventListener('click', this._onClick);
|
||||
window.removeEventListener('mousedown', this._onMouseDown);
|
||||
window.removeEventListener('mouseover', this._onMouseOver);
|
||||
};
|
||||
|
||||
onHookOperations = (operations: Uint32Array) => {
|
||||
debug('onHookOperations', operations);
|
||||
|
||||
@@ -107,4 +132,33 @@ export default class Agent extends EventEmitter {
|
||||
// this._bridge.send('operations', operations, [operations.buffer]);
|
||||
this._bridge.send('operations', operations);
|
||||
};
|
||||
|
||||
_onClick = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.stopInspectingDOM();
|
||||
this._bridge.send('stopInspectingDOM');
|
||||
};
|
||||
|
||||
_onMouseDown = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const target = ((event.target: any): HTMLElement);
|
||||
const id = this.getIDForNode(target);
|
||||
|
||||
if (id !== null) {
|
||||
this._bridge.send('selectFiber', id);
|
||||
}
|
||||
};
|
||||
|
||||
_onMouseOver = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const target = ((event.target: any): HTMLElement);
|
||||
|
||||
showOverlay(target, target.tagName.toLowerCase());
|
||||
};
|
||||
}
|
||||
+12
-5
@@ -826,11 +826,18 @@ export function attach(
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function getReactElementFromNative(hostInstance) {
|
||||
const fiber = renderer.findFiberByHostInstance(hostInstance);
|
||||
function getFiberIDFromNative(
|
||||
hostInstance,
|
||||
findNearestUnfilteredAncestor = false
|
||||
) {
|
||||
let fiber = renderer.findFiberByHostInstance(hostInstance);
|
||||
if (fiber != null) {
|
||||
const primaryFiber = getPrimaryFiber(((fiber: any): Fiber));
|
||||
return primaryFiber;
|
||||
if (findNearestUnfilteredAncestor) {
|
||||
while (fiber !== null && shouldFilterFiber(fiber)) {
|
||||
fiber = fiber.return;
|
||||
}
|
||||
}
|
||||
return getFiberID(getPrimaryFiber(((fiber: any): Fiber)));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1150,8 +1157,8 @@ export function attach(
|
||||
}
|
||||
|
||||
return {
|
||||
getFiberIDFromNative,
|
||||
getNativeFromReactElement,
|
||||
getReactElementFromNative,
|
||||
handleCommitFiberRoot,
|
||||
handleCommitFiberUnmount,
|
||||
inspectElement,
|
||||
|
||||
@@ -46,7 +46,10 @@ export type ReactRenderer = {
|
||||
export type RendererInterface = {
|
||||
cleanup: () => void,
|
||||
getNativeFromReactElement?: ?(component: Fiber) => ?NativeType,
|
||||
getReactElementFromNative?: ?(component: NativeType) => ?Fiber,
|
||||
getFiberIDFromNative: (
|
||||
component: NativeType,
|
||||
findNearestUnfilteredAncestor?: boolean
|
||||
) => number | null,
|
||||
handleCommitFiberRoot: (fiber: Object) => void,
|
||||
handleCommitFiberUnmount: (fiber: Object) => void,
|
||||
inspectElement: (id: number) => InspectedElement | null,
|
||||
|
||||
@@ -130,7 +130,7 @@ export default class Store extends EventEmitter {
|
||||
const element = this._idToElement.get(id);
|
||||
|
||||
if (element == null) {
|
||||
console.warn(`No element found with id "${id}`);
|
||||
console.warn(`No element found with id "${id}"`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { BridgeContext } from './context';
|
||||
import Toggle from './Toggle';
|
||||
import ButtonIcon from './ButtonIcon';
|
||||
|
||||
export default function InspectHostNodesToggle() {
|
||||
const [isInspecting, setIsInspecting] = useState(false);
|
||||
const bridge = useContext(BridgeContext);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(isChecked: boolean) => {
|
||||
setIsInspecting(isChecked);
|
||||
|
||||
if (isChecked) {
|
||||
bridge.send('startInspectingDOM');
|
||||
} else {
|
||||
bridge.send('stopInspectingDOM');
|
||||
}
|
||||
},
|
||||
[bridge]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const onStopInspectingDOM = () => setIsInspecting(false);
|
||||
bridge.addListener('stopInspectingDOM', onStopInspectingDOM);
|
||||
return () =>
|
||||
bridge.removeListener('stopInspectingDOM', onStopInspectingDOM);
|
||||
}, [bridge]);
|
||||
|
||||
return (
|
||||
<Toggle
|
||||
onChange={handleChange}
|
||||
isChecked={isInspecting}
|
||||
title="Select an element in the page to inspect it"
|
||||
>
|
||||
<ButtonIcon type="search" />
|
||||
</Toggle>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
.ToggleDisabled,
|
||||
.ToggleOn,
|
||||
.ToggleOff {
|
||||
border-radius: 0.25rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem;
|
||||
cursor: pointer;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.ToggleOff {
|
||||
border: none;
|
||||
background: var(--color-button-background);
|
||||
color: var(--color-button);
|
||||
}
|
||||
.ToggleOff:hover {
|
||||
background: var(--color-button-background-hover);
|
||||
color: var(--color-button-hover);
|
||||
}
|
||||
|
||||
.ToggleOn,
|
||||
.ToggleOn:active,
|
||||
.ToggleOn:focus {
|
||||
background: var(--color-button-background-focus);
|
||||
color: var(--color-button-focus);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ToggleDisabled {
|
||||
background: var(--color-button-background);
|
||||
color: var(--color-button-disabled);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.Input {
|
||||
width: 0;
|
||||
margin: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import styles from './Toggle.css';
|
||||
|
||||
type Props = {
|
||||
children: React$Node,
|
||||
className?: string,
|
||||
isChecked: boolean,
|
||||
isDisabled?: boolean,
|
||||
onChange: (isChecked: boolean) => void,
|
||||
};
|
||||
|
||||
export default function Toggle({
|
||||
children,
|
||||
className = '',
|
||||
isDisabled = false,
|
||||
isChecked,
|
||||
onChange,
|
||||
}: Props) {
|
||||
let defaultClassName;
|
||||
if (isDisabled) {
|
||||
defaultClassName = styles.ToggleDisabled;
|
||||
} else if (isChecked) {
|
||||
defaultClassName = styles.ToggleOn;
|
||||
} else {
|
||||
defaultClassName = styles.ToggleOff;
|
||||
}
|
||||
|
||||
const handleChange = useCallback(
|
||||
({ target }) => {
|
||||
onChange(target.checked);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<label className={`${defaultClassName} ${className}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
className={styles.Input}
|
||||
checked={isChecked}
|
||||
disabled={isDisabled}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
@@ -11,9 +11,8 @@ import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import { TreeContext } from './TreeContext';
|
||||
import { SettingsContext } from './SettingsContext';
|
||||
import Button from './Button';
|
||||
import ButtonIcon from './ButtonIcon';
|
||||
import Element from './Element';
|
||||
import InspectHostNodesToggle from './InspectHostNodesToggle';
|
||||
import OwnersStack from './OwnersStack';
|
||||
import SearchInput from './SearchInput';
|
||||
|
||||
@@ -85,12 +84,7 @@ export default function Tree(props: Props) {
|
||||
<div className={styles.Tree}>
|
||||
<div className={styles.SearchInput}>
|
||||
{ownerStack.length > 0 ? <OwnersStack /> : <SearchInput />}
|
||||
<Button
|
||||
className={styles.IconButton}
|
||||
title="Select an element in the page to inspect it"
|
||||
>
|
||||
<ButtonIcon type="search" />
|
||||
</Button>
|
||||
<InspectHostNodesToggle />
|
||||
</div>
|
||||
<div className={styles.AutoSizerWrapper}>
|
||||
<AutoSizer>
|
||||
|
||||
@@ -26,7 +26,7 @@ import React, {
|
||||
useReducer,
|
||||
} from 'react';
|
||||
import { createRegExp } from './utils';
|
||||
import { StoreContext } from './context';
|
||||
import { BridgeContext, StoreContext } from './context';
|
||||
import Store from '../store';
|
||||
|
||||
import type { Element } from 'src/devtools/types';
|
||||
@@ -470,7 +470,9 @@ function reduceOwnersState(store: Store, state: State, action: Action): State {
|
||||
|
||||
// TODO Remove TreeContextController wrapper element once global ConsearchText.write API exists.
|
||||
function TreeContextController({ children }: {| children: React$Node |}) {
|
||||
const bridge = useContext(BridgeContext);
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
const initialRevision = useMemo(() => store.revision, [store]);
|
||||
|
||||
// This reducer is created inline because it needs access to the Store.
|
||||
@@ -596,6 +598,14 @@ function TreeContextController({ children }: {| children: React$Node |}) {
|
||||
[state]
|
||||
);
|
||||
|
||||
// Listen for host element selections.
|
||||
useEffect(() => {
|
||||
const handleSelectFiber = (id: number) =>
|
||||
dispatch({ type: 'SELECT_ELEMENT_BY_ID', payload: id });
|
||||
bridge.addListener('selectFiber', handleSelectFiber);
|
||||
return () => bridge.removeListener('selectFiber', handleSelectFiber);
|
||||
}, [bridge, dispatch]);
|
||||
|
||||
// Mutations to the underlying tree may impact this context (e.g. search results, selection state).
|
||||
useEffect(() => {
|
||||
const handleStoreMutated = ([
|
||||
@@ -622,7 +632,7 @@ function TreeContextController({ children }: {| children: React$Node |}) {
|
||||
store.addListener('mutated', handleStoreMutated);
|
||||
|
||||
return () => store.removeListener('mutated', handleStoreMutated);
|
||||
}, [store]);
|
||||
}, [dispatch, store]);
|
||||
|
||||
return <TreeContext.Provider value={value}>{children}</TreeContext.Provider>;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user