Compare commits
3 Commits
v19.2.1
...
gh/mofeiZ/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02732c11c6 | ||
|
|
9af74e024c | ||
|
|
b658948159 |
@@ -284,6 +284,19 @@ export function createHOCFilter(isEnabled: boolean = true) {
|
||||
};
|
||||
}
|
||||
|
||||
export function createEnvironmentNameFilter(
|
||||
env: string,
|
||||
isEnabled: boolean = true,
|
||||
) {
|
||||
const Types = require('react-devtools-shared/src/frontend/types');
|
||||
return {
|
||||
type: Types.ComponentFilterEnvironmentName,
|
||||
isEnabled,
|
||||
isValid: true,
|
||||
value: env,
|
||||
};
|
||||
}
|
||||
|
||||
export function createElementTypeFilter(
|
||||
elementType: ElementType,
|
||||
isEnabled: boolean = true,
|
||||
|
||||
@@ -220,6 +220,7 @@ export default class Agent extends EventEmitter<{
|
||||
this.updateConsolePatchSettings,
|
||||
);
|
||||
bridge.addListener('updateComponentFilters', this.updateComponentFilters);
|
||||
bridge.addListener('getEnvironmentNames', this.getEnvironmentNames);
|
||||
|
||||
// Temporarily support older standalone front-ends sending commands to newer embedded backends.
|
||||
// We do this because React Native embeds the React DevTools backend,
|
||||
@@ -814,6 +815,24 @@ export default class Agent extends EventEmitter<{
|
||||
}
|
||||
};
|
||||
|
||||
getEnvironmentNames: () => void = () => {
|
||||
let accumulatedNames = null;
|
||||
for (const rendererID in this._rendererInterfaces) {
|
||||
const renderer = this._rendererInterfaces[+rendererID];
|
||||
const names = renderer.getEnvironmentNames();
|
||||
if (accumulatedNames === null) {
|
||||
accumulatedNames = names;
|
||||
} else {
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
if (accumulatedNames.indexOf(names[i]) === -1) {
|
||||
accumulatedNames.push(names[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this._bridge.send('environmentNames', accumulatedNames || []);
|
||||
};
|
||||
|
||||
onTraceUpdates: (nodes: Set<HostInstance>) => void = nodes => {
|
||||
this.emit('traceUpdates', nodes);
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
ComponentFilterElementType,
|
||||
ComponentFilterHOC,
|
||||
ComponentFilterLocation,
|
||||
ComponentFilterEnvironmentName,
|
||||
ElementTypeClass,
|
||||
ElementTypeContext,
|
||||
ElementTypeFunction,
|
||||
@@ -721,6 +722,11 @@ export function getInternalReactConstants(version: string): {
|
||||
};
|
||||
}
|
||||
|
||||
// All environment names we've seen so far. This lets us create a list of filters to apply.
|
||||
// This should ideally include env of filtered Components too so that you can add those as
|
||||
// filters at the same time as removing some other filter.
|
||||
const knownEnvironmentNames: Set<string> = new Set();
|
||||
|
||||
// Map of one or more Fibers in a pair to their unique id number.
|
||||
// We track both Fibers to support Fast Refresh,
|
||||
// which may forcefully replace one of the pair as part of hot reloading.
|
||||
@@ -1099,6 +1105,7 @@ export function attach(
|
||||
const hideElementsWithDisplayNames: Set<RegExp> = new Set();
|
||||
const hideElementsWithPaths: Set<RegExp> = new Set();
|
||||
const hideElementsWithTypes: Set<ElementType> = new Set();
|
||||
const hideElementsWithEnvs: Set<string> = new Set();
|
||||
|
||||
// Highlight updates
|
||||
let traceUpdatesEnabled: boolean = false;
|
||||
@@ -1108,6 +1115,7 @@ export function attach(
|
||||
hideElementsWithTypes.clear();
|
||||
hideElementsWithDisplayNames.clear();
|
||||
hideElementsWithPaths.clear();
|
||||
hideElementsWithEnvs.clear();
|
||||
|
||||
componentFilters.forEach(componentFilter => {
|
||||
if (!componentFilter.isEnabled) {
|
||||
@@ -1133,6 +1141,9 @@ export function attach(
|
||||
case ComponentFilterHOC:
|
||||
hideElementsWithDisplayNames.add(new RegExp('\\('));
|
||||
break;
|
||||
case ComponentFilterEnvironmentName:
|
||||
hideElementsWithEnvs.add(componentFilter.value);
|
||||
break;
|
||||
default:
|
||||
console.warn(
|
||||
`Invalid component filter type "${componentFilter.type}"`,
|
||||
@@ -1215,7 +1226,14 @@ export function attach(
|
||||
flushPendingEvents();
|
||||
}
|
||||
|
||||
function shouldFilterVirtual(data: ReactComponentInfo): boolean {
|
||||
function getEnvironmentNames(): Array<string> {
|
||||
return Array.from(knownEnvironmentNames);
|
||||
}
|
||||
|
||||
function shouldFilterVirtual(
|
||||
data: ReactComponentInfo,
|
||||
secondaryEnv: null | string,
|
||||
): boolean {
|
||||
// For purposes of filtering Server Components are always Function Components.
|
||||
// Environment will be used to filter Server vs Client.
|
||||
// Technically they can be forwardRef and memo too but those filters will go away
|
||||
@@ -1236,6 +1254,14 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(data.env == null || hideElementsWithEnvs.has(data.env)) &&
|
||||
(secondaryEnv === null || hideElementsWithEnvs.has(secondaryEnv))
|
||||
) {
|
||||
// If a Component has two environments, you have to filter both for it not to appear.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1294,6 +1320,26 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
if (hideElementsWithEnvs.has('Client')) {
|
||||
// If we're filtering out the Client environment we should filter out all
|
||||
// "Client Components". Technically that also includes the built-ins but
|
||||
// since that doesn't actually include any additional code loading it's
|
||||
// useful to not filter out the built-ins. Those can be filtered separately.
|
||||
// There's no other way to filter out just Function components on the Client.
|
||||
// Therefore, this only filters Class and Function components.
|
||||
switch (tag) {
|
||||
case ClassComponent:
|
||||
case IncompleteClassComponent:
|
||||
case IncompleteFunctionComponent:
|
||||
case FunctionComponent:
|
||||
case IndeterminateComponent:
|
||||
case ForwardRef:
|
||||
case MemoComponent:
|
||||
case SimpleMemoComponent:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* DISABLED: https://github.com/facebook/react/pull/28417
|
||||
if (hideElementsWithPaths.size > 0) {
|
||||
const source = getSourceForFiber(fiber);
|
||||
@@ -2489,7 +2535,14 @@ export function attach(
|
||||
}
|
||||
// Scan up until the next Component to see if this component changed environment.
|
||||
const componentInfo: ReactComponentInfo = (debugEntry: any);
|
||||
if (shouldFilterVirtual(componentInfo)) {
|
||||
const secondaryEnv = getSecondaryEnvironmentName(fiber._debugInfo, i);
|
||||
if (componentInfo.env != null) {
|
||||
knownEnvironmentNames.add(componentInfo.env);
|
||||
}
|
||||
if (secondaryEnv !== null) {
|
||||
knownEnvironmentNames.add(secondaryEnv);
|
||||
}
|
||||
if (shouldFilterVirtual(componentInfo, secondaryEnv)) {
|
||||
// Skip.
|
||||
continue;
|
||||
}
|
||||
@@ -2511,10 +2564,6 @@ export function attach(
|
||||
);
|
||||
}
|
||||
previousVirtualInstance = createVirtualInstance(componentInfo);
|
||||
const secondaryEnv = getSecondaryEnvironmentName(
|
||||
fiber._debugInfo,
|
||||
i,
|
||||
);
|
||||
recordVirtualMount(
|
||||
previousVirtualInstance,
|
||||
reconcilingParent,
|
||||
@@ -2919,7 +2968,17 @@ export function attach(
|
||||
continue;
|
||||
}
|
||||
const componentInfo: ReactComponentInfo = (debugEntry: any);
|
||||
if (shouldFilterVirtual(componentInfo)) {
|
||||
const secondaryEnv = getSecondaryEnvironmentName(
|
||||
nextChild._debugInfo,
|
||||
i,
|
||||
);
|
||||
if (componentInfo.env != null) {
|
||||
knownEnvironmentNames.add(componentInfo.env);
|
||||
}
|
||||
if (secondaryEnv !== null) {
|
||||
knownEnvironmentNames.add(secondaryEnv);
|
||||
}
|
||||
if (shouldFilterVirtual(componentInfo, secondaryEnv)) {
|
||||
continue;
|
||||
}
|
||||
if (level === virtualLevel) {
|
||||
@@ -2983,10 +3042,6 @@ export function attach(
|
||||
} else {
|
||||
// Otherwise we create a new instance.
|
||||
const newVirtualInstance = createVirtualInstance(componentInfo);
|
||||
const secondaryEnv = getSecondaryEnvironmentName(
|
||||
nextChild._debugInfo,
|
||||
i,
|
||||
);
|
||||
recordVirtualMount(
|
||||
newVirtualInstance,
|
||||
reconcilingParent,
|
||||
@@ -3338,6 +3393,18 @@ export function attach(
|
||||
// I.e. we just restore them by undoing what we did above.
|
||||
fiberInstance.firstChild = remainingReconcilingChildren;
|
||||
remainingReconcilingChildren = null;
|
||||
|
||||
if (traceUpdatesEnabled) {
|
||||
// If we're tracing updates and we've bailed out before reaching a host node,
|
||||
// we should fall back to recursively marking the nearest host descendants for highlight.
|
||||
if (traceNearestHostComponentUpdate) {
|
||||
const hostInstances =
|
||||
findAllCurrentHostInstances(fiberInstance);
|
||||
hostInstances.forEach(hostInstance => {
|
||||
traceUpdatesForNodes.add(hostInstance);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If this fiber is filtered there might be changes to this set elsewhere so we have
|
||||
// to visit each child to place it back in the set. We let the child bail out instead.
|
||||
@@ -3349,19 +3416,6 @@ export function attach(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (traceUpdatesEnabled) {
|
||||
// If we're tracing updates and we've bailed out before reaching a host node,
|
||||
// we should fall back to recursively marking the nearest host descendants for highlight.
|
||||
if (traceNearestHostComponentUpdate) {
|
||||
const hostInstances = findAllCurrentHostInstances(
|
||||
getFiberInstanceThrows(nextFiber),
|
||||
);
|
||||
hostInstances.forEach(hostInstance => {
|
||||
traceUpdatesForNodes.add(hostInstance);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3635,15 +3689,31 @@ export function attach(
|
||||
return null;
|
||||
}
|
||||
|
||||
function findAllCurrentHostInstances(
|
||||
fiberInstance: FiberInstance,
|
||||
): $ReadOnlyArray<HostInstance> {
|
||||
const hostInstances = [];
|
||||
const fiber = fiberInstance.data;
|
||||
if (!fiber) {
|
||||
return hostInstances;
|
||||
function appendHostInstancesByDevToolsInstance(
|
||||
devtoolsInstance: DevToolsInstance,
|
||||
hostInstances: Array<HostInstance>,
|
||||
) {
|
||||
if (devtoolsInstance.kind === FIBER_INSTANCE) {
|
||||
const fiber = devtoolsInstance.data;
|
||||
appendHostInstancesByFiber(fiber, hostInstances);
|
||||
return;
|
||||
}
|
||||
// Search the tree for the nearest child Fiber and add all its host instances.
|
||||
// TODO: If the true nearest Fiber is filtered, we might skip it and instead include all
|
||||
// the children below it. In the extreme case, searching the whole tree.
|
||||
for (
|
||||
let child = devtoolsInstance.firstChild;
|
||||
child !== null;
|
||||
child = child.nextSibling
|
||||
) {
|
||||
appendHostInstancesByDevToolsInstance(child, hostInstances);
|
||||
}
|
||||
}
|
||||
|
||||
function appendHostInstancesByFiber(
|
||||
fiber: Fiber,
|
||||
hostInstances: Array<HostInstance>,
|
||||
): void {
|
||||
// Next we'll drill down this component to find all HostComponent/Text.
|
||||
let node: Fiber = fiber;
|
||||
while (true) {
|
||||
@@ -3663,19 +3733,24 @@ export function attach(
|
||||
continue;
|
||||
}
|
||||
if (node === fiber) {
|
||||
return hostInstances;
|
||||
return;
|
||||
}
|
||||
while (!node.sibling) {
|
||||
if (!node.return || node.return === fiber) {
|
||||
return hostInstances;
|
||||
return;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
node.sibling.return = node.return;
|
||||
node = node.sibling;
|
||||
}
|
||||
// Flow needs the return here, but ESLint complains about it.
|
||||
// eslint-disable-next-line no-unreachable
|
||||
}
|
||||
|
||||
function findAllCurrentHostInstances(
|
||||
devtoolsInstance: DevToolsInstance,
|
||||
): $ReadOnlyArray<HostInstance> {
|
||||
const hostInstances: Array<HostInstance> = [];
|
||||
appendHostInstancesByDevToolsInstance(devtoolsInstance, hostInstances);
|
||||
return hostInstances;
|
||||
}
|
||||
|
||||
@@ -3686,17 +3761,7 @@ export function attach(
|
||||
console.warn(`Could not find DevToolsInstance with id "${id}"`);
|
||||
return null;
|
||||
}
|
||||
if (devtoolsInstance.kind !== FIBER_INSTANCE) {
|
||||
// TODO: Handle VirtualInstance.
|
||||
return null;
|
||||
}
|
||||
const fiber = devtoolsInstance.data;
|
||||
if (fiber === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hostInstances = findAllCurrentHostInstances(devtoolsInstance);
|
||||
return hostInstances;
|
||||
return findAllCurrentHostInstances(devtoolsInstance);
|
||||
} catch (err) {
|
||||
// The fiber might have unmounted by now.
|
||||
return null;
|
||||
@@ -3925,7 +3990,7 @@ export function attach(
|
||||
owner = ownerFiber._debugOwner;
|
||||
} else {
|
||||
const ownerInfo: ReactComponentInfo = (owner: any); // Refined
|
||||
if (!shouldFilterVirtual(ownerInfo)) {
|
||||
if (!shouldFilterVirtual(ownerInfo, null)) {
|
||||
return ownerInfo;
|
||||
}
|
||||
owner = ownerInfo.owner;
|
||||
@@ -5750,5 +5815,6 @@ export function attach(
|
||||
storeAsGlobal,
|
||||
unpatchConsoleForStrictMode,
|
||||
updateComponentFilters,
|
||||
getEnvironmentNames,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1078,6 +1078,11 @@ export function attach(
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
function getEnvironmentNames(): Array<string> {
|
||||
// No RSC support.
|
||||
return [];
|
||||
}
|
||||
|
||||
function setTraceUpdatesEnabled(enabled: boolean) {
|
||||
// Not implemented.
|
||||
}
|
||||
@@ -1152,5 +1157,6 @@ export function attach(
|
||||
storeAsGlobal,
|
||||
unpatchConsoleForStrictMode,
|
||||
updateComponentFilters,
|
||||
getEnvironmentNames,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -416,6 +416,7 @@ export type RendererInterface = {
|
||||
) => void,
|
||||
unpatchConsoleForStrictMode: () => void,
|
||||
updateComponentFilters: (componentFilters: Array<ComponentFilter>) => void,
|
||||
getEnvironmentNames: () => Array<string>,
|
||||
|
||||
// Timeline profiler interface
|
||||
|
||||
|
||||
2
packages/react-devtools-shared/src/bridge.js
vendored
2
packages/react-devtools-shared/src/bridge.js
vendored
@@ -189,6 +189,7 @@ export type BackendEvents = {
|
||||
operations: [Array<number>],
|
||||
ownersList: [OwnersList],
|
||||
overrideComponentFilters: [Array<ComponentFilter>],
|
||||
environmentNames: [Array<string>],
|
||||
profilingData: [ProfilingDataBackend],
|
||||
profilingStatus: [boolean],
|
||||
reloadAppForProfiling: [],
|
||||
@@ -237,6 +238,7 @@ type FrontendEvents = {
|
||||
stopProfiling: [],
|
||||
storeAsGlobal: [StoreAsGlobalParams],
|
||||
updateComponentFilters: [Array<ComponentFilter>],
|
||||
getEnvironmentNames: [],
|
||||
updateConsolePatchSettings: [ConsolePatchSettings],
|
||||
viewAttributeSource: [ViewAttributeSourceParams],
|
||||
viewElementSource: [ElementAndRendererID],
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
use,
|
||||
} from 'react';
|
||||
import {
|
||||
LOCAL_STORAGE_OPEN_IN_EDITOR_URL,
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
ComponentFilterElementType,
|
||||
ComponentFilterHOC,
|
||||
ComponentFilterLocation,
|
||||
ComponentFilterEnvironmentName,
|
||||
ElementTypeClass,
|
||||
ElementTypeContext,
|
||||
ElementTypeFunction,
|
||||
@@ -52,11 +54,16 @@ import type {
|
||||
ElementType,
|
||||
ElementTypeComponentFilter,
|
||||
RegExpComponentFilter,
|
||||
EnvironmentNameComponentFilter,
|
||||
} from 'react-devtools-shared/src/frontend/types';
|
||||
|
||||
const vscodeFilepath = 'vscode://file/{path}:{line}';
|
||||
|
||||
export default function ComponentsSettings(_: {}): React.Node {
|
||||
export default function ComponentsSettings({
|
||||
environmentNames,
|
||||
}: {
|
||||
environmentNames: Promise<Array<string>>,
|
||||
}): React.Node {
|
||||
const store = useContext(StoreContext);
|
||||
const {parseHookNames, setParseHookNames} = useContext(SettingsContext);
|
||||
|
||||
@@ -101,6 +108,30 @@ export default function ComponentsSettings(_: {}): React.Node {
|
||||
Array<ComponentFilter>,
|
||||
>(() => [...store.componentFilters]);
|
||||
|
||||
const usedEnvironmentNames = use(environmentNames);
|
||||
|
||||
const resolvedEnvironmentNames = useMemo(() => {
|
||||
const set = new Set(usedEnvironmentNames);
|
||||
// If there are other filters already specified but are not currently
|
||||
// on the page, we still allow them as options.
|
||||
for (let i = 0; i < componentFilters.length; i++) {
|
||||
const filter = componentFilters[i];
|
||||
if (filter.type === ComponentFilterEnvironmentName) {
|
||||
set.add(filter.value);
|
||||
}
|
||||
}
|
||||
// Client is special and is always available as a default.
|
||||
if (set.size > 0) {
|
||||
// Only show any options at all if there's any other option already
|
||||
// used by a filter or if any environments are used by the page.
|
||||
// Note that "Client" can have been added above which would mean
|
||||
// that we should show it as an option regardless if it's the only
|
||||
// option.
|
||||
set.add('Client');
|
||||
}
|
||||
return Array.from(set).sort();
|
||||
}, [usedEnvironmentNames, componentFilters]);
|
||||
|
||||
const addFilter = useCallback(() => {
|
||||
setComponentFilters(prevComponentFilters => {
|
||||
return [
|
||||
@@ -146,6 +177,13 @@ export default function ComponentsSettings(_: {}): React.Node {
|
||||
isEnabled: componentFilter.isEnabled,
|
||||
isValid: true,
|
||||
};
|
||||
} else if (type === ComponentFilterEnvironmentName) {
|
||||
cloned[index] = {
|
||||
type: ComponentFilterEnvironmentName,
|
||||
isEnabled: componentFilter.isEnabled,
|
||||
isValid: true,
|
||||
value: 'Client',
|
||||
};
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
@@ -210,6 +248,29 @@ export default function ComponentsSettings(_: {}): React.Node {
|
||||
[],
|
||||
);
|
||||
|
||||
const updateFilterValueEnvironmentName = useCallback(
|
||||
(componentFilter: ComponentFilter, value: string) => {
|
||||
if (componentFilter.type !== ComponentFilterEnvironmentName) {
|
||||
throw Error('Invalid value for environment name filter');
|
||||
}
|
||||
|
||||
setComponentFilters(prevComponentFilters => {
|
||||
const cloned: Array<ComponentFilter> = [...prevComponentFilters];
|
||||
if (componentFilter.type === ComponentFilterEnvironmentName) {
|
||||
const index = prevComponentFilters.indexOf(componentFilter);
|
||||
if (index >= 0) {
|
||||
cloned[index] = {
|
||||
...componentFilter,
|
||||
value,
|
||||
};
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const removeFilter = useCallback((index: number) => {
|
||||
setComponentFilters(prevComponentFilters => {
|
||||
const cloned: Array<ComponentFilter> = [...prevComponentFilters];
|
||||
@@ -246,6 +307,11 @@ export default function ComponentsSettings(_: {}): React.Node {
|
||||
...((cloned[index]: any): BooleanComponentFilter),
|
||||
isEnabled,
|
||||
};
|
||||
} else if (componentFilter.type === ComponentFilterEnvironmentName) {
|
||||
cloned[index] = {
|
||||
...((cloned[index]: any): EnvironmentNameComponentFilter),
|
||||
isEnabled,
|
||||
};
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
@@ -380,10 +446,16 @@ export default function ComponentsSettings(_: {}): React.Node {
|
||||
<option value={ComponentFilterDisplayName}>name</option>
|
||||
<option value={ComponentFilterElementType}>type</option>
|
||||
<option value={ComponentFilterHOC}>hoc</option>
|
||||
{resolvedEnvironmentNames.length > 0 && (
|
||||
<option value={ComponentFilterEnvironmentName}>
|
||||
environment
|
||||
</option>
|
||||
)}
|
||||
</select>
|
||||
</td>
|
||||
<td className={styles.TableCell}>
|
||||
{componentFilter.type === ComponentFilterElementType &&
|
||||
{(componentFilter.type === ComponentFilterElementType ||
|
||||
componentFilter.type === ComponentFilterEnvironmentName) &&
|
||||
'equals'}
|
||||
{(componentFilter.type === ComponentFilterLocation ||
|
||||
componentFilter.type === ComponentFilterDisplayName) &&
|
||||
@@ -428,6 +500,23 @@ export default function ComponentsSettings(_: {}): React.Node {
|
||||
value={componentFilter.value}
|
||||
/>
|
||||
)}
|
||||
{componentFilter.type === ComponentFilterEnvironmentName && (
|
||||
<select
|
||||
className={styles.Select}
|
||||
value={componentFilter.value}
|
||||
onChange={({currentTarget}) =>
|
||||
updateFilterValueEnvironmentName(
|
||||
componentFilter,
|
||||
currentTarget.value,
|
||||
)
|
||||
}>
|
||||
{resolvedEnvironmentNames.map(name => (
|
||||
<option key={name} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</td>
|
||||
<td className={styles.TableCell}>
|
||||
<Button
|
||||
|
||||
@@ -58,7 +58,8 @@ export default function SettingsModal(_: {}): React.Node {
|
||||
}
|
||||
|
||||
function SettingsModalImpl(_: {}) {
|
||||
const {setIsModalShowing} = useContext(SettingsModalContext);
|
||||
const {setIsModalShowing, environmentNames} =
|
||||
useContext(SettingsModalContext);
|
||||
const dismissModal = useCallback(
|
||||
() => setIsModalShowing(false),
|
||||
[setIsModalShowing],
|
||||
@@ -81,7 +82,7 @@ function SettingsModalImpl(_: {}) {
|
||||
let view = null;
|
||||
switch (selectedTabID) {
|
||||
case 'components':
|
||||
view = <ComponentsSettings />;
|
||||
view = <ComponentsSettings environmentNames={environmentNames} />;
|
||||
break;
|
||||
// $FlowFixMe[incompatible-type] is this missing in TabID?
|
||||
case 'debugging':
|
||||
|
||||
@@ -10,7 +10,16 @@
|
||||
import type {ReactContext} from 'shared/ReactTypes';
|
||||
|
||||
import * as React from 'react';
|
||||
import {createContext, useMemo, useState} from 'react';
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useCallback,
|
||||
useState,
|
||||
startTransition,
|
||||
} from 'react';
|
||||
|
||||
import {BridgeContext} from '../context';
|
||||
import type {FrontendBridge} from '../../../bridge';
|
||||
|
||||
export type DisplayDensity = 'comfortable' | 'compact';
|
||||
export type Theme = 'auto' | 'light' | 'dark';
|
||||
@@ -18,7 +27,7 @@ export type Theme = 'auto' | 'light' | 'dark';
|
||||
type Context = {
|
||||
isModalShowing: boolean,
|
||||
setIsModalShowing: (value: boolean) => void,
|
||||
...
|
||||
environmentNames: null | Promise<Array<string>>,
|
||||
};
|
||||
|
||||
const SettingsModalContext: ReactContext<Context> = createContext<Context>(
|
||||
@@ -26,20 +35,42 @@ const SettingsModalContext: ReactContext<Context> = createContext<Context>(
|
||||
);
|
||||
SettingsModalContext.displayName = 'SettingsModalContext';
|
||||
|
||||
function fetchEnvironmentNames(bridge: FrontendBridge): Promise<Array<string>> {
|
||||
return new Promise(resolve => {
|
||||
function onEnvironmentNames(names: Array<string>) {
|
||||
bridge.removeListener('environmentNames', onEnvironmentNames);
|
||||
resolve(names);
|
||||
}
|
||||
bridge.addListener('environmentNames', onEnvironmentNames);
|
||||
bridge.send('getEnvironmentNames');
|
||||
});
|
||||
}
|
||||
|
||||
function SettingsModalContextController({
|
||||
children,
|
||||
}: {
|
||||
children: React$Node,
|
||||
}): React.Node {
|
||||
const [isModalShowing, setIsModalShowing] = useState<boolean>(false);
|
||||
const bridge = useContext(BridgeContext);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({isModalShowing, setIsModalShowing}),
|
||||
[isModalShowing, setIsModalShowing],
|
||||
);
|
||||
const setIsModalShowing: boolean => void = useCallback((value: boolean) => {
|
||||
startTransition(() => {
|
||||
setContext({
|
||||
isModalShowing: value,
|
||||
setIsModalShowing,
|
||||
environmentNames: value ? fetchEnvironmentNames(bridge) : null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const [currentContext, setContext] = useState<Context>({
|
||||
isModalShowing: false,
|
||||
setIsModalShowing,
|
||||
environmentNames: null,
|
||||
});
|
||||
|
||||
return (
|
||||
<SettingsModalContext.Provider value={value}>
|
||||
<SettingsModalContext.Provider value={currentContext}>
|
||||
{children}
|
||||
</SettingsModalContext.Provider>
|
||||
);
|
||||
|
||||
@@ -76,8 +76,9 @@ export const ComponentFilterElementType = 1;
|
||||
export const ComponentFilterDisplayName = 2;
|
||||
export const ComponentFilterLocation = 3;
|
||||
export const ComponentFilterHOC = 4;
|
||||
export const ComponentFilterEnvironmentName = 5;
|
||||
|
||||
export type ComponentFilterType = 1 | 2 | 3 | 4;
|
||||
export type ComponentFilterType = 1 | 2 | 3 | 4 | 5;
|
||||
|
||||
// Hide all elements of types in this Set.
|
||||
// We hide host components only by default.
|
||||
@@ -102,10 +103,18 @@ export type BooleanComponentFilter = {
|
||||
type: 4,
|
||||
};
|
||||
|
||||
export type EnvironmentNameComponentFilter = {
|
||||
isEnabled: boolean,
|
||||
isValid: boolean,
|
||||
type: 5,
|
||||
value: string,
|
||||
};
|
||||
|
||||
export type ComponentFilter =
|
||||
| BooleanComponentFilter
|
||||
| ElementTypeComponentFilter
|
||||
| RegExpComponentFilter;
|
||||
| RegExpComponentFilter
|
||||
| EnvironmentNameComponentFilter;
|
||||
|
||||
export type HookName = string | null;
|
||||
// Map of hook source ("<filename>:<line-number>:<column-number>") to name.
|
||||
|
||||
28
packages/react-reconciler/src/ReactFiber.js
vendored
28
packages/react-reconciler/src/ReactFiber.js
vendored
@@ -404,10 +404,16 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
|
||||
workInProgress.dependencies =
|
||||
currentDependencies === null
|
||||
? null
|
||||
: {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
};
|
||||
: __DEV__
|
||||
? {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
_debugThenableState: currentDependencies._debugThenableState,
|
||||
}
|
||||
: {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
};
|
||||
|
||||
// These will be overridden during the parent's reconciliation
|
||||
workInProgress.sibling = current.sibling;
|
||||
@@ -503,10 +509,16 @@ export function resetWorkInProgress(
|
||||
workInProgress.dependencies =
|
||||
currentDependencies === null
|
||||
? null
|
||||
: {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
};
|
||||
: __DEV__
|
||||
? {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
_debugThenableState: currentDependencies._debugThenableState,
|
||||
}
|
||||
: {
|
||||
lanes: currentDependencies.lanes,
|
||||
firstContext: currentDependencies.firstContext,
|
||||
};
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// Note: We don't reset the actualTime counts. It's useful to accumulate
|
||||
|
||||
12
packages/react-reconciler/src/ReactFiberHooks.js
vendored
12
packages/react-reconciler/src/ReactFiberHooks.js
vendored
@@ -637,6 +637,18 @@ function finishRenderingHooks<Props, SecondArg>(
|
||||
): void {
|
||||
if (__DEV__) {
|
||||
workInProgress._debugHookTypes = hookTypesDev;
|
||||
// Stash the thenable state for use by DevTools.
|
||||
if (workInProgress.dependencies === null) {
|
||||
if (thenableState !== null) {
|
||||
workInProgress.dependencies = {
|
||||
lanes: NoLanes,
|
||||
firstContext: null,
|
||||
_debugThenableState: thenableState,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
workInProgress.dependencies._debugThenableState = thenableState;
|
||||
}
|
||||
}
|
||||
|
||||
// We can assume the previous dispatcher is always this one, since we set it
|
||||
|
||||
@@ -825,10 +825,16 @@ function readContextForConsumer_withSelect<C>(
|
||||
|
||||
// This is the first dependency for this component. Create a new list.
|
||||
lastContextDependency = contextItem;
|
||||
consumer.dependencies = {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
};
|
||||
consumer.dependencies = __DEV__
|
||||
? {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
_debugThenableState: null,
|
||||
}
|
||||
: {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
};
|
||||
if (enableLazyContextPropagation) {
|
||||
consumer.flags |= NeedsPropagation;
|
||||
}
|
||||
@@ -869,10 +875,16 @@ function readContextForConsumer<C>(
|
||||
|
||||
// This is the first dependency for this component. Create a new list.
|
||||
lastContextDependency = contextItem;
|
||||
consumer.dependencies = {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
};
|
||||
consumer.dependencies = __DEV__
|
||||
? {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
_debugThenableState: null,
|
||||
}
|
||||
: {
|
||||
lanes: NoLanes,
|
||||
firstContext: contextItem,
|
||||
};
|
||||
if (enableLazyContextPropagation) {
|
||||
consumer.flags |= NeedsPropagation;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import type {
|
||||
} from './ReactFiberTracingMarkerComponent';
|
||||
import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates';
|
||||
import type {ComponentStackNode} from 'react-server/src/ReactFizzComponentStack';
|
||||
import type {ThenableState} from './ReactFiberThenable';
|
||||
|
||||
// Unwind Circular: moved from ReactFiberHooks.old
|
||||
export type HookType =
|
||||
@@ -81,7 +82,7 @@ export type Dependencies = {
|
||||
| ContextDependency<mixed>
|
||||
| ContextDependencyWithSelect<mixed>
|
||||
| null,
|
||||
...
|
||||
_debugThenableState?: null | ThenableState, // DEV-only
|
||||
};
|
||||
|
||||
export type MemoCache = {
|
||||
|
||||
Reference in New Issue
Block a user