mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Added filter UI (but with a lot of $FlowFixMe comments)
This commit is contained in:
@@ -16,7 +16,7 @@ import type {
|
||||
RendererID,
|
||||
RendererInterface,
|
||||
} from './types';
|
||||
import type { Bridge, FilterPreferences } from '../types';
|
||||
import type { Bridge, ComponentFilter } from '../types';
|
||||
|
||||
const debug = (methodName, ...args) => {
|
||||
if (__DEBUG__) {
|
||||
@@ -118,7 +118,7 @@ export default class Agent extends EventEmitter {
|
||||
this.syncSelectionFromNativeElementsPanel
|
||||
);
|
||||
bridge.addListener('shutdown', this.shutdown);
|
||||
bridge.addListener('updateFilterPreferences', this.updateFilterPreferences);
|
||||
bridge.addListener('updateComponentFilters', this.updateComponentFilters);
|
||||
bridge.addListener('viewElementSource', this.viewElementSource);
|
||||
|
||||
if (this._isProfiling) {
|
||||
@@ -490,12 +490,12 @@ export default class Agent extends EventEmitter {
|
||||
this._bridge.send('profilingStatus', this._isProfiling);
|
||||
};
|
||||
|
||||
updateFilterPreferences = (filterPreferences: FilterPreferences) => {
|
||||
updateComponentFilters = (componentFilters: Array<ComponentFilter>) => {
|
||||
for (let rendererID in this._rendererInterfaces) {
|
||||
const renderer = ((this._rendererInterfaces[
|
||||
(rendererID: any)
|
||||
]: any): RendererInterface);
|
||||
renderer.updateFilterPreferences(filterPreferences);
|
||||
renderer.updateComponentFilters(componentFilters);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+49
-13
@@ -2,6 +2,9 @@
|
||||
|
||||
import { gte } from 'semver';
|
||||
import {
|
||||
ComponentFilterDisplayName,
|
||||
ComponentFilterElementType,
|
||||
ComponentFilterPath,
|
||||
ElementTypeClass,
|
||||
ElementTypeContext,
|
||||
ElementTypeEventComponent,
|
||||
@@ -17,7 +20,7 @@ import {
|
||||
} from 'src/types';
|
||||
import {
|
||||
getDisplayName,
|
||||
getSavedFilterPreferences,
|
||||
getSavedComponentFilters,
|
||||
getUID,
|
||||
utfEncodeString,
|
||||
} from 'src/utils';
|
||||
@@ -47,7 +50,7 @@ import type {
|
||||
RendererInterface,
|
||||
} from './types';
|
||||
import type { InspectedElement } from 'src/devtools/views/Components/types';
|
||||
import type { ElementType, FilterPreferences } from 'src/types';
|
||||
import type { ComponentFilter, ElementType } from 'src/types';
|
||||
|
||||
function getInternalReactConstants(version) {
|
||||
const ReactSymbols = {
|
||||
@@ -261,17 +264,53 @@ export function attach(
|
||||
}
|
||||
};
|
||||
|
||||
let {
|
||||
hideElementsWithTypes,
|
||||
hideElementsWithDisplayNames,
|
||||
hideElementsWithPaths,
|
||||
} = getSavedFilterPreferences();
|
||||
// Configurable Components tree filters.
|
||||
const hideElementsWithDisplayNames: Set<RegExp> = new Set();
|
||||
const hideElementsWithPaths: Set<RegExp> = new Set();
|
||||
const hideElementsWithTypes: Set<ElementType> = new Set();
|
||||
|
||||
function applyComponentFilters(componentFilters: Array<ComponentFilter>) {
|
||||
hideElementsWithTypes.clear();
|
||||
hideElementsWithDisplayNames.clear();
|
||||
hideElementsWithPaths.clear();
|
||||
|
||||
componentFilters.forEach(componentFilter => {
|
||||
if (!componentFilter.isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (componentFilter.type) {
|
||||
case ComponentFilterDisplayName:
|
||||
if (componentFilter.isValid && componentFilter.value !== '') {
|
||||
hideElementsWithDisplayNames.add(
|
||||
new RegExp(componentFilter.value, 'i')
|
||||
);
|
||||
}
|
||||
break;
|
||||
case ComponentFilterElementType:
|
||||
hideElementsWithTypes.add(componentFilter.value);
|
||||
break;
|
||||
case ComponentFilterPath:
|
||||
if (componentFilter.isValid && componentFilter.value !== '') {
|
||||
hideElementsWithPaths.add(new RegExp(componentFilter.value, 'i'));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.warn(
|
||||
`Invalid component filter type "${componentFilter.type}"`
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
applyComponentFilters(getSavedComponentFilters());
|
||||
|
||||
// TODO (filter) Should we make this operation more efficient?
|
||||
// For example, we could add a new recursive unmount tree operation.
|
||||
// The unmount operations are already significantly smaller than mount opreations though.
|
||||
// This is something to keep in mind for later.
|
||||
function updateFilterPreferences(filterPreferences: FilterPreferences) {
|
||||
function updateComponentFilters(componentFilters: Array<ComponentFilter>) {
|
||||
if (this._isProfiling) {
|
||||
// Re-mounting a tree while profiling is in progress might break a lot of assumptions.
|
||||
// If necessary, we could support this- but it doesn't seem like a necessary use case.
|
||||
@@ -286,10 +325,7 @@ export function attach(
|
||||
currentRootID = -1;
|
||||
});
|
||||
|
||||
hideElementsWithTypes = filterPreferences.hideElementsWithTypes;
|
||||
hideElementsWithDisplayNames =
|
||||
filterPreferences.hideElementsWithDisplayNames;
|
||||
hideElementsWithPaths = filterPreferences.hideElementsWithPaths;
|
||||
applyComponentFilters(componentFilters);
|
||||
|
||||
// Recursively re-mount all roots with new filter criteria applied.
|
||||
hook.getFiberRoots(rendererID).forEach(root => {
|
||||
@@ -2280,6 +2316,6 @@ export function attach(
|
||||
setTrackedPath,
|
||||
startProfiling,
|
||||
stopProfiling,
|
||||
updateFilterPreferences,
|
||||
updateComponentFilters,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import type { ElementType, FilterPreferences } from 'src/types';
|
||||
import type { ComponentFilter, ElementType } from 'src/types';
|
||||
import type { InspectedElement } from 'src/devtools/views/Components/types';
|
||||
|
||||
type BundleType =
|
||||
@@ -139,7 +139,7 @@ export type RendererInterface = {
|
||||
setTrackedPath: (path: Array<PathFrame> | null) => void,
|
||||
startProfiling: () => void,
|
||||
stopProfiling: () => void,
|
||||
updateFilterPreferences: (filterPreferences: FilterPreferences) => void,
|
||||
updateComponentFilters: (somponentFilters: Array<ComponentFilter>) => void,
|
||||
};
|
||||
|
||||
export type Handler = (data: any) => void;
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ export const TREE_OPERATION_REORDER_CHILDREN = 3;
|
||||
export const TREE_OPERATION_UPDATE_TREE_BASE_DURATION = 4;
|
||||
|
||||
export const LOCAL_STORAGE_FILTER_PREFERENCES_KEY =
|
||||
'React::DevTools::filterPreferences';
|
||||
'React::DevTools::componentFilters';
|
||||
|
||||
export const LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY =
|
||||
'React::DevTools::reloadAndProfile';
|
||||
|
||||
+12
-12
@@ -11,8 +11,8 @@ import {
|
||||
} from '../constants';
|
||||
import { ElementTypeRoot } from '../types';
|
||||
import {
|
||||
getSavedFilterPreferences,
|
||||
saveFilterPreferences,
|
||||
getSavedComponentFilters,
|
||||
saveComponentFilters,
|
||||
utfDecodeString,
|
||||
} from '../utils';
|
||||
import { __DEBUG__ } from '../constants';
|
||||
@@ -24,7 +24,7 @@ import type {
|
||||
ImportedProfilingData,
|
||||
ProfilingSnapshotNode,
|
||||
} from './views/Profiler/types';
|
||||
import type { Bridge, ElementType, FilterPreferences } from '../types';
|
||||
import type { Bridge, ComponentFilter, ElementType } from '../types';
|
||||
|
||||
const debug = (methodName, ...args) => {
|
||||
if (__DEBUG__) {
|
||||
@@ -69,7 +69,7 @@ export default class Store extends EventEmitter {
|
||||
// Should new nodes be collapsed by default when added to the tree?
|
||||
_collapseNodesByDefault: boolean = true;
|
||||
|
||||
_filterPreferences: FilterPreferences;
|
||||
_componentFilters: Array<ComponentFilter>;
|
||||
|
||||
// At least one of the injected renderers contains (DEV only) owner metadata.
|
||||
_hasOwnerMetadata: boolean = false;
|
||||
@@ -143,7 +143,7 @@ export default class Store extends EventEmitter {
|
||||
localStorage.getItem(LOCAL_STORAGE_COLLAPSE_ROOTS_BY_DEFAULT_KEY) !==
|
||||
'false';
|
||||
|
||||
this._filterPreferences = getSavedFilterPreferences();
|
||||
this._componentFilters = getSavedComponentFilters();
|
||||
|
||||
if (config != null) {
|
||||
const {
|
||||
@@ -239,26 +239,26 @@ export default class Store extends EventEmitter {
|
||||
this.emit('collapseNodesByDefault');
|
||||
}
|
||||
|
||||
get filterPreferences(): FilterPreferences {
|
||||
return this._filterPreferences;
|
||||
get componentFilters(): Array<ComponentFilter> {
|
||||
return this._componentFilters;
|
||||
}
|
||||
set filterPreferences(value: FilterPreferences): void {
|
||||
set componentFilters(value: Array<ComponentFilter>): void {
|
||||
if (this._isProfiling) {
|
||||
// Re-mounting a tree while profiling is in progress might break a lot of assumptions.
|
||||
// If necessary, we could support this- but it doesn't seem like a necessary use case.
|
||||
throw Error('Cannot modify filter preferences while profiling');
|
||||
}
|
||||
|
||||
this._filterPreferences = value;
|
||||
this._componentFilters = value;
|
||||
|
||||
// Update persisted filter preferences stored in localStorage.
|
||||
saveFilterPreferences(value);
|
||||
saveComponentFilters(value);
|
||||
|
||||
// Notify the renderer that filter prefernces have changed.
|
||||
// This is an expensive opreation; it unmounts and remounts the entire tree.
|
||||
this._bridge.send('updateFilterPreferences', value);
|
||||
this._bridge.send('updateComponentFilters', value);
|
||||
|
||||
this.emit('filterPreferences');
|
||||
this.emit('componentFilters');
|
||||
}
|
||||
|
||||
get hasOwnerMetadata(): boolean {
|
||||
|
||||
@@ -9,13 +9,13 @@ import tooltipStyles from './Tooltip.css';
|
||||
type Props = {
|
||||
children: React$Node,
|
||||
className?: string,
|
||||
title: string,
|
||||
title?: string,
|
||||
};
|
||||
|
||||
export default function Button({
|
||||
children,
|
||||
className = '',
|
||||
title,
|
||||
title = '',
|
||||
...rest
|
||||
}: Props) {
|
||||
let button = (
|
||||
|
||||
@@ -6,6 +6,7 @@ import styles from './ButtonIcon.css';
|
||||
export type IconType =
|
||||
| 'add'
|
||||
| 'cancel'
|
||||
| 'clear'
|
||||
| 'close'
|
||||
| 'collapsed'
|
||||
| 'copy'
|
||||
@@ -21,7 +22,11 @@ export type IconType =
|
||||
| 'previous'
|
||||
| 'record'
|
||||
| 'reload'
|
||||
| 'save'
|
||||
| 'search'
|
||||
| 'settings'
|
||||
| 'toggle_off'
|
||||
| 'toggle_on'
|
||||
| 'undo'
|
||||
| 'up'
|
||||
| 'view-dom'
|
||||
@@ -41,6 +46,9 @@ export default function ButtonIcon({ className = '', type }: Props) {
|
||||
case 'cancel':
|
||||
pathData = PATH_CANCEL;
|
||||
break;
|
||||
case 'clear':
|
||||
pathData = PATH_CLEAR;
|
||||
break;
|
||||
case 'close':
|
||||
pathData = PATH_CLOSE;
|
||||
break;
|
||||
@@ -86,9 +94,21 @@ export default function ButtonIcon({ className = '', type }: Props) {
|
||||
case 'reload':
|
||||
pathData = PATH_RELOAD;
|
||||
break;
|
||||
case 'save':
|
||||
pathData = PATH_SAVE;
|
||||
break;
|
||||
case 'search':
|
||||
pathData = PATH_SEARCH;
|
||||
break;
|
||||
case 'settings':
|
||||
pathData = PATH_SETTINGS;
|
||||
break;
|
||||
case 'toggle_off':
|
||||
pathData = PATH_TOGGLE_OFF;
|
||||
break;
|
||||
case 'toggle_on':
|
||||
pathData = PATH_TOGGLE_ON;
|
||||
break;
|
||||
case 'undo':
|
||||
pathData = PATH_UNDO;
|
||||
break;
|
||||
@@ -120,9 +140,14 @@ export default function ButtonIcon({ className = '', type }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const PATH_ADD = 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z';
|
||||
const PATH_ADD =
|
||||
'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z';
|
||||
|
||||
const PATH_CANCEL = `
|
||||
M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z
|
||||
`;
|
||||
|
||||
const PATH_CLEAR = `
|
||||
M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69
|
||||
16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z
|
||||
`;
|
||||
@@ -138,8 +163,8 @@ const PATH_COPY = `
|
||||
`;
|
||||
|
||||
const PATH_DELETE = `
|
||||
M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zm2.46-7.12l1.41-1.41L12 12.59l2.12-2.12 1.41 1.41L13.41 14l2.12
|
||||
2.12-1.41 1.41L12 15.41l-2.12 2.12-1.41-1.41L10.59 14l-2.13-2.12zM15.5 4l-1-1h-5l-1 1H5v2h14V4z
|
||||
M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12
|
||||
13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z
|
||||
`;
|
||||
|
||||
const PATH_DOWN = 'M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z';
|
||||
@@ -177,11 +202,30 @@ const PATH_RELOAD = `
|
||||
3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z
|
||||
`;
|
||||
|
||||
const PATH_SAVE = `
|
||||
M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z
|
||||
`;
|
||||
|
||||
const PATH_SEARCH = `
|
||||
M8.5,22H3.7l-1.4-1.5V3.8l1.3-1.5h17.2l1,1.5v4.9h-1.3V4.3l-0.4-0.6H4.2L3.6,4.3V20l0.7,0.7h4.2V22z
|
||||
M23,13.9l-4.6,3.6l4.6,4.6l-1.1,1.1l-4.7-4.4l-3.3,4.4l-3.2-12.3L23,13.9z
|
||||
`;
|
||||
|
||||
const PATH_SETTINGS = `
|
||||
M15.95 10.78c.03-.25.05-.51.05-.78s-.02-.53-.06-.78l1.69-1.32c.15-.12.19-.34.1-.51l-1.6-2.77c-.1-.18-.31-.24-.49-.18l-1.99.8c-.42-.32-.86-.58-1.35-.78L12
|
||||
2.34c-.03-.2-.2-.34-.4-.34H8.4c-.2 0-.36.14-.39.34l-.3 2.12c-.49.2-.94.47-1.35.78l-1.99-.8c-.18-.07-.39
|
||||
0-.49.18l-1.6 2.77c-.1.18-.06.39.1.51l1.69
|
||||
1.32c-.04.25-.07.52-.07.78s.02.53.06.78L2.37 12.1c-.15.12-.19.34-.1.51l1.6 2.77c.1.18.31.24.49.18l1.99-.8c.42.32.86.58
|
||||
1.35.78l.3 2.12c.04.2.2.34.4.34h3.2c.2 0 .37-.14.39-.34l.3-2.12c.49-.2.94-.47 1.35-.78l1.99.8c.18.07.39 0
|
||||
.49-.18l1.6-2.77c.1-.18.06-.39-.1-.51l-1.67-1.32zM10 13c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z
|
||||
`;
|
||||
|
||||
const PATH_TOGGLE_OFF =
|
||||
'M17 7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h10c2.76 0 5-2.24 5-5s-2.24-5-5-5zM7 15c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z';
|
||||
|
||||
const PATH_TOGGLE_ON =
|
||||
'M17 7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h10c2.76 0 5-2.24 5-5s-2.24-5-5-5zm0 8c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z';
|
||||
|
||||
const PATH_UNDO = `
|
||||
M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88
|
||||
3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
.Background {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
background-color: var(--color-modal-background);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.Modal {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
min-width: 20rem;
|
||||
max-width: 100%;
|
||||
display: inline-block;
|
||||
background-color: var(--color-background);
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.LeftRight {
|
||||
display: flex;
|
||||
}
|
||||
.Left {
|
||||
}
|
||||
.Right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.ButtonIcon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.NoFiltersCell {
|
||||
padding: 0.25rem 0;
|
||||
color: var(--color-dim);
|
||||
}
|
||||
|
||||
.Table {
|
||||
min-width: 20rem;
|
||||
margin-top: 0.5rem;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.TableRow {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.TableCell {
|
||||
padding: 0;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
.TableCell:last-of-type {
|
||||
text-align: right;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.Select {
|
||||
}
|
||||
|
||||
.Input {
|
||||
border: none;
|
||||
border-radius: 0.125rem;
|
||||
padding: 0.125rem;
|
||||
}
|
||||
|
||||
.CancelButton {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.InvalidRegExp,
|
||||
.InvalidRegExp:active,
|
||||
.InvalidRegExp:focus,
|
||||
.InvalidRegExp:hover {
|
||||
color: var(--color-value-invalid);
|
||||
}
|
||||
|
||||
.ToggleOffInvalid,
|
||||
.ToggleOnInvalid,
|
||||
.ToggleOff,
|
||||
.ToggleOn {
|
||||
border-radius: 0.75rem;
|
||||
width: 1rem;
|
||||
height: 0.625rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.125rem;
|
||||
}
|
||||
.ToggleOffInvalid {
|
||||
background-color: var(--color-toggle-background-invalid);
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.ToggleOnInvalid {
|
||||
background-color: var(--color-toggle-background-invalid);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.ToggleOff {
|
||||
background-color: var(--color-toggle-background-off);
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.ToggleOn {
|
||||
background-color: var(--color-toggle-background-on);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.ToggleInsideOff,
|
||||
.ToggleInsideOn {
|
||||
border-radius: 0.375rem;
|
||||
width: 0.375rem;
|
||||
height: 0.375rem;
|
||||
background-color: var(--color-toggle-text);
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
// @flow
|
||||
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useModalDismissSignal, useSubscription } from '../hooks';
|
||||
import { ComponentFiltersModalContext } from './ComponentFiltersModalContext';
|
||||
import { StoreContext } from '../context';
|
||||
import Button from '../Button';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import Toggle from '../Toggle';
|
||||
import Store from 'src/devtools/store';
|
||||
import {
|
||||
ComponentFilterElementType,
|
||||
ComponentFilterDisplayName,
|
||||
ComponentFilterPath,
|
||||
ElementTypeClass,
|
||||
ElementTypeContext,
|
||||
ElementTypeEventTarget,
|
||||
ElementTypeFunction,
|
||||
ElementTypeForwardRef,
|
||||
ElementTypeHostComponent,
|
||||
ElementTypeMemo,
|
||||
ElementTypeOtherOrUnknown,
|
||||
ElementTypeProfiler,
|
||||
ElementTypeSuspense,
|
||||
} from 'src/types';
|
||||
import styles from './ComponentFiltersModal.css';
|
||||
|
||||
import type {
|
||||
ComponentFilter,
|
||||
ElementType,
|
||||
ElementTypeComponentFilter,
|
||||
FilterType,
|
||||
RegExpComponentFilter,
|
||||
} from 'src/types';
|
||||
|
||||
export default function ComponentFiltersModalWrapper(_: {||}) {
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
const { isModalShowing, setIsModalShowing } = useContext(
|
||||
ComponentFiltersModalContext
|
||||
);
|
||||
|
||||
// Re-mounting a tree while profiling is in progress might break a lot of assumptions.
|
||||
// If necessary, we could support this- but it doesn't seem like a necessary use case.
|
||||
const isProfilingSubscription = useMemo(
|
||||
() => ({
|
||||
getCurrentValue: () => store.isProfiling,
|
||||
subscribe: (callback: Function) => {
|
||||
store.addListener('isProfiling', callback);
|
||||
return () => store.removeListener('isProfiling', callback);
|
||||
},
|
||||
}),
|
||||
[store]
|
||||
);
|
||||
const isProfiling = useSubscription<boolean, Store>(isProfilingSubscription);
|
||||
if (isProfiling && isModalShowing) {
|
||||
setIsModalShowing(false);
|
||||
}
|
||||
|
||||
return isModalShowing ? (
|
||||
<ComponentFiltersModal
|
||||
store={store}
|
||||
setIsModalShowing={setIsModalShowing}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
||||
type Props = {|
|
||||
store: Store,
|
||||
setIsModalShowing: (value: boolean) => void,
|
||||
|};
|
||||
|
||||
function ComponentFiltersModal({ store, setIsModalShowing }: Props) {
|
||||
const dismissModal = useCallback(() => setIsModalShowing(false), [
|
||||
setIsModalShowing,
|
||||
]);
|
||||
|
||||
const modalRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useModalDismissSignal(modalRef, dismissModal);
|
||||
|
||||
const {
|
||||
addFilter,
|
||||
changeFilterType,
|
||||
updateFilterValueElementType,
|
||||
updateFilterValueRegExp,
|
||||
componentFilters,
|
||||
removeFilter,
|
||||
saveFilters,
|
||||
toggleFilterIsEnabled,
|
||||
} = useComponentFilters();
|
||||
|
||||
const saveAndClose = useCallback(() => {
|
||||
saveFilters();
|
||||
dismissModal();
|
||||
}, [dismissModal, saveFilters]);
|
||||
|
||||
return (
|
||||
<div className={styles.Background}>
|
||||
<div className={styles.Modal} ref={modalRef}>
|
||||
<div className={styles.LeftRight}>
|
||||
<div className={styles.Left}>Hide components where...</div>
|
||||
<div className={styles.Right}>
|
||||
<Button onClick={addFilter}>
|
||||
<ButtonIcon className={styles.ButtonIcon} type="add" />
|
||||
Add filter
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<table className={styles.Table}>
|
||||
<tbody>
|
||||
{componentFilters.length === 0 && (
|
||||
<tr className={styles.TableRow}>
|
||||
<td className={styles.NoFiltersCell}>
|
||||
No filters have been added.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{componentFilters.map((componentFilter, index) => (
|
||||
<tr className={styles.TableRow} key={index}>
|
||||
<td className={styles.TableCell}>
|
||||
<Toggle
|
||||
className={
|
||||
componentFilter.isValid !== false
|
||||
? ''
|
||||
: styles.InvalidRegExp
|
||||
}
|
||||
isChecked={componentFilter.isEnabled}
|
||||
onChange={isEnabled =>
|
||||
toggleFilterIsEnabled(componentFilter, isEnabled)
|
||||
}
|
||||
title={
|
||||
componentFilter.isValid === false
|
||||
? 'Filter invalid'
|
||||
: componentFilter.isEnabled
|
||||
? 'Filter enabled'
|
||||
: 'Filter disabled'
|
||||
}
|
||||
>
|
||||
<ToggleIcon
|
||||
isEnabled={componentFilter.isEnabled}
|
||||
isValid={
|
||||
componentFilter.isValid == null ||
|
||||
componentFilter.isValid === true
|
||||
}
|
||||
/>
|
||||
</Toggle>
|
||||
</td>
|
||||
<td className={styles.TableCell}>
|
||||
<select
|
||||
className={styles.Select}
|
||||
value={componentFilter.type}
|
||||
onChange={({ currentTarget }) =>
|
||||
changeFilterType(
|
||||
componentFilter,
|
||||
// $FlowFixMe TODO (filters)
|
||||
parseInt(currentTarget.value, 10)
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value={ComponentFilterElementType}>type</option>
|
||||
<option value={ComponentFilterDisplayName}>name</option>
|
||||
<option value={ComponentFilterPath}>path</option>
|
||||
</select>
|
||||
</td>
|
||||
<td className={styles.TableCell}>
|
||||
{componentFilter.type === ComponentFilterElementType
|
||||
? 'equals'
|
||||
: 'matches'}
|
||||
</td>
|
||||
<td className={styles.TableCell}>
|
||||
{componentFilter.type === ComponentFilterElementType ? (
|
||||
<select
|
||||
className={styles.Select}
|
||||
value={componentFilter.value}
|
||||
onChange={({ currentTarget }) =>
|
||||
updateFilterValueElementType(
|
||||
componentFilter,
|
||||
// $FlowFixMe TODO (filters)
|
||||
parseInt(currentTarget.value, 10)
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value={ElementTypeClass}>class</option>
|
||||
<option value={ElementTypeContext}>context</option>
|
||||
<option value={ElementTypeEventTarget}>event</option>
|
||||
<option value={ElementTypeFunction}>function</option>
|
||||
<option value={ElementTypeForwardRef}>forward ref</option>
|
||||
<option value={ElementTypeHostComponent}>
|
||||
host (e.g. <div>)
|
||||
</option>
|
||||
<option value={ElementTypeMemo}>memo</option>
|
||||
<option value={ElementTypeOtherOrUnknown}>other</option>
|
||||
<option value={ElementTypeProfiler}>profiler</option>
|
||||
<option value={ElementTypeSuspense}>suspense</option>
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
className={styles.Input}
|
||||
type="text"
|
||||
placeholder="Regular expression"
|
||||
onChange={({ currentTarget }) =>
|
||||
updateFilterValueRegExp(
|
||||
componentFilter,
|
||||
currentTarget.value
|
||||
)
|
||||
}
|
||||
value={componentFilter.value}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className={styles.TableCell}>
|
||||
<Button
|
||||
onClick={() => removeFilter(index)}
|
||||
title="Delete filter"
|
||||
>
|
||||
<ButtonIcon type="delete" />
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className={styles.LeftRight}>
|
||||
<div className={styles.Right}>
|
||||
<Button className={styles.CancelButton} onClick={dismissModal}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={saveAndClose}>Save Changes</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type ToggleIconProps = {|
|
||||
isEnabled: boolean,
|
||||
isValid: boolean,
|
||||
|};
|
||||
function ToggleIcon({ isEnabled, isValid }: ToggleIconProps) {
|
||||
let className;
|
||||
if (isValid) {
|
||||
className = isEnabled ? styles.ToggleOn : styles.ToggleOff;
|
||||
} else {
|
||||
className = isEnabled ? styles.ToggleOnInvalid : styles.ToggleOffInvalid;
|
||||
}
|
||||
return (
|
||||
<div className={className}>
|
||||
<div
|
||||
className={isEnabled ? styles.ToggleInsideOn : styles.ToggleInsideOff}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function useComponentFilters() {
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
const [componentFilters, setComponentFilters] = useState<
|
||||
Array<ComponentFilter>
|
||||
>(() => [...store.componentFilters]);
|
||||
|
||||
const addFilter = useCallback(() => {
|
||||
setComponentFilters(componentFilters => {
|
||||
return [
|
||||
...componentFilters,
|
||||
{
|
||||
type: ComponentFilterElementType,
|
||||
value: ElementTypeHostComponent,
|
||||
isEnabled: true,
|
||||
},
|
||||
];
|
||||
});
|
||||
}, []);
|
||||
|
||||
const changeFilterType = useCallback(
|
||||
(componentFilter: ComponentFilter, type: FilterType) => {
|
||||
setComponentFilters(componentFilters => {
|
||||
const cloned: Array<ComponentFilter> = [...componentFilters];
|
||||
const index = componentFilters.indexOf(componentFilter);
|
||||
if (index >= 0) {
|
||||
if (type === ComponentFilterElementType) {
|
||||
// $FlowFixMe TODO (filters)
|
||||
cloned[index] = ({
|
||||
type,
|
||||
isEnabled: componentFilter.isEnabled,
|
||||
value: ElementTypeHostComponent,
|
||||
}: ElementTypeComponentFilter);
|
||||
} else if (
|
||||
type === ComponentFilterDisplayName ||
|
||||
type === ComponentFilterPath
|
||||
) {
|
||||
// $FlowFixMe TODO (filters)
|
||||
cloned[index] = ({
|
||||
type,
|
||||
isEnabled: componentFilter.isEnabled,
|
||||
isValid: true,
|
||||
value: '',
|
||||
}: RegExpComponentFilter);
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const updateFilterValueElementType = useCallback(
|
||||
(componentFilter: ComponentFilter, value: ElementType) => {
|
||||
if (componentFilter.type !== ComponentFilterElementType) {
|
||||
throw Error('Invalid value for element type filter');
|
||||
}
|
||||
|
||||
setComponentFilters(componentFilters => {
|
||||
const cloned: Array<ComponentFilter> = [...componentFilters];
|
||||
const index = componentFilters.indexOf(componentFilter);
|
||||
if (index >= 0) {
|
||||
// $FlowFixMe TODO (filters)
|
||||
cloned[index] = {
|
||||
...componentFilter,
|
||||
value,
|
||||
};
|
||||
}
|
||||
return cloned;
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const updateFilterValueRegExp = useCallback(
|
||||
(componentFilter: ComponentFilter, value: string) => {
|
||||
if (componentFilter.type === ComponentFilterElementType) {
|
||||
throw Error('Invalid value for element type filter');
|
||||
}
|
||||
|
||||
setComponentFilters(componentFilters => {
|
||||
const cloned: Array<ComponentFilter> = [...componentFilters];
|
||||
const index = componentFilters.indexOf(componentFilter);
|
||||
if (index >= 0) {
|
||||
let isValid = true;
|
||||
try {
|
||||
new RegExp(value);
|
||||
} catch (error) {
|
||||
isValid = false;
|
||||
}
|
||||
// $FlowFixMe TODO (filters)
|
||||
cloned[index] = {
|
||||
...componentFilter,
|
||||
isValid,
|
||||
value,
|
||||
};
|
||||
}
|
||||
return cloned;
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const removeFilter = useCallback((index: number) => {
|
||||
setComponentFilters(componentFilters => {
|
||||
const cloned: Array<ComponentFilter> = [...componentFilters];
|
||||
cloned.splice(index, 1);
|
||||
return cloned;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const saveFilters = useCallback(() => {
|
||||
store.componentFilters = [...componentFilters];
|
||||
}, [componentFilters, store]);
|
||||
|
||||
const toggleFilterIsEnabled = useCallback(
|
||||
(componentFilter: ComponentFilter, isEnabled: boolean) => {
|
||||
setComponentFilters(componentFilters => {
|
||||
const cloned: Array<ComponentFilter> = [...componentFilters];
|
||||
const index = componentFilters.indexOf(componentFilter);
|
||||
if (index >= 0) {
|
||||
// $FlowFixMe TODO (filters)
|
||||
cloned[index] = {
|
||||
...cloned[index],
|
||||
isEnabled,
|
||||
};
|
||||
}
|
||||
return cloned;
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
addFilter,
|
||||
changeFilterType,
|
||||
componentFilters,
|
||||
removeFilter,
|
||||
saveFilters,
|
||||
toggleFilterIsEnabled,
|
||||
updateFilterValueElementType,
|
||||
updateFilterValueRegExp,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// @flow
|
||||
|
||||
import React, { createContext, useMemo, useState } from 'react';
|
||||
|
||||
type Context = {|
|
||||
isModalShowing: boolean,
|
||||
setIsModalShowing: (value: boolean) => void,
|
||||
|};
|
||||
|
||||
const ComponentFiltersModalContext = createContext<Context>(
|
||||
((null: any): Context)
|
||||
);
|
||||
ComponentFiltersModalContext.displayName = 'ComponentFiltersModalContext';
|
||||
|
||||
type Props = {|
|
||||
children: React$Node,
|
||||
|};
|
||||
|
||||
function ComponentFiltersModalContextController({ children }: Props) {
|
||||
const [isModalShowing, setIsModalShowing] = useState(false);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
isModalShowing,
|
||||
setIsModalShowing,
|
||||
}),
|
||||
[isModalShowing]
|
||||
);
|
||||
|
||||
return (
|
||||
<ComponentFiltersModalContext.Provider value={value}>
|
||||
{children}
|
||||
</ComponentFiltersModalContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export { ComponentFiltersModalContext, ComponentFiltersModalContextController };
|
||||
@@ -0,0 +1,42 @@
|
||||
// @flow
|
||||
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import { useSubscription } from '../hooks';
|
||||
import { ComponentFiltersModalContext } from './ComponentFiltersModalContext';
|
||||
import { StoreContext } from '../context';
|
||||
import Toggle from '../Toggle';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import Store from 'src/devtools/store';
|
||||
|
||||
export default function ToggleCommitFilterModalButton() {
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
const { isModalShowing, setIsModalShowing } = useContext(
|
||||
ComponentFiltersModalContext
|
||||
);
|
||||
|
||||
// Re-mounting a tree while profiling is in progress might break a lot of assumptions.
|
||||
// If necessary, we could support this- but it doesn't seem like a necessary use case.
|
||||
const isProfilingSubscription = useMemo(
|
||||
() => ({
|
||||
getCurrentValue: () => store.isProfiling,
|
||||
subscribe: (callback: Function) => {
|
||||
store.addListener('isProfiling', callback);
|
||||
return () => store.removeListener('isProfiling', callback);
|
||||
},
|
||||
}),
|
||||
[store]
|
||||
);
|
||||
const isProfiling = useSubscription<boolean, Store>(isProfilingSubscription);
|
||||
|
||||
return (
|
||||
<Toggle
|
||||
isChecked={isModalShowing}
|
||||
isDisabled={isProfiling}
|
||||
onChange={setIsModalShowing}
|
||||
title="Filter preferences"
|
||||
>
|
||||
<ButtonIcon type="filter" />
|
||||
</Toggle>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
.Tree {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -18,6 +18,9 @@ import ElementView from './Element';
|
||||
import InspectHostNodesToggle from './InspectHostNodesToggle';
|
||||
import OwnersStack from './OwnersStack';
|
||||
import SearchInput from './SearchInput';
|
||||
import { ComponentFiltersModalContextController } from './ComponentFiltersModalContext';
|
||||
import ToggleComponentFiltersModalButton from './ToggleComponentFiltersModalButton';
|
||||
import ComponentFiltersModal from './ComponentFiltersModal';
|
||||
|
||||
import styles from './Tree.css';
|
||||
|
||||
@@ -269,42 +272,47 @@ export default function Tree(props: Props) {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.Tree} ref={treeRef}>
|
||||
<div className={styles.SearchInput}>
|
||||
<InspectHostNodesToggle />
|
||||
<div className={styles.VRule} />
|
||||
{ownerStack.length > 0 ? <OwnersStack /> : <SearchInput />}
|
||||
<ComponentFiltersModalContextController>
|
||||
<div className={styles.Tree} ref={treeRef}>
|
||||
<div className={styles.SearchInput}>
|
||||
<InspectHostNodesToggle />
|
||||
<div className={styles.VRule} />
|
||||
{ownerStack.length > 0 ? <OwnersStack /> : <SearchInput />}
|
||||
<div className={styles.VRule} />
|
||||
<ToggleComponentFiltersModalButton />
|
||||
</div>
|
||||
<div
|
||||
className={styles.AutoSizerWrapper}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
onKeyPress={handleKeyPress}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
ref={focusTargetRef}
|
||||
tabIndex={0}
|
||||
>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
// $FlowFixMe https://github.com/facebook/flow/issues/7341
|
||||
<FixedSizeList
|
||||
className={styles.List}
|
||||
height={height}
|
||||
innerElementType={InnerElementType}
|
||||
itemCount={numElements}
|
||||
itemData={itemData}
|
||||
itemSize={lineHeight}
|
||||
overscanCount={3}
|
||||
ref={listRef}
|
||||
width={width}
|
||||
>
|
||||
{ElementView}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
<ComponentFiltersModal />
|
||||
</div>
|
||||
<div
|
||||
className={styles.AutoSizerWrapper}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
onKeyPress={handleKeyPress}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
ref={focusTargetRef}
|
||||
tabIndex={0}
|
||||
>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
// $FlowFixMe https://github.com/facebook/flow/issues/7341
|
||||
<FixedSizeList
|
||||
className={styles.List}
|
||||
height={height}
|
||||
innerElementType={InnerElementType}
|
||||
itemCount={numElements}
|
||||
itemData={itemData}
|
||||
itemSize={lineHeight}
|
||||
overscanCount={3}
|
||||
ref={listRef}
|
||||
width={width}
|
||||
>
|
||||
{ElementView}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentFiltersModalContextController>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function ClearProfilingDataButton() {
|
||||
onClick={clear}
|
||||
title="Clear profiling data"
|
||||
>
|
||||
<ButtonIcon type="cancel" />
|
||||
<ButtonIcon type="clear" />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback, useContext, useMemo } from 'react';
|
||||
import { ElementTypeHostComponent } from 'src/types';
|
||||
import { useSubscription } from '../hooks';
|
||||
import { StoreContext } from '../context';
|
||||
import { SettingsContext } from './SettingsContext';
|
||||
@@ -10,8 +9,6 @@ import portaledContent from '../portaledContent';
|
||||
|
||||
import styles from './Settings.css';
|
||||
|
||||
import type { FilterPreferences } from 'src/types';
|
||||
|
||||
function Settings(_: {||}) {
|
||||
const store = useContext(StoreContext);
|
||||
const { displayDensity, setDisplayDensity, theme, setTheme } = useContext(
|
||||
@@ -46,49 +43,6 @@ function Settings(_: {||}) {
|
||||
collapseNodesByDefaultSubscription
|
||||
);
|
||||
|
||||
// Re-mounting a tree while profiling is in progress might break a lot of assumptions.
|
||||
// If necessary, we could support this- but it doesn't seem like a necessary use case.
|
||||
const isProfilingSubscription = useMemo(
|
||||
() => ({
|
||||
getCurrentValue: () => store.isProfiling,
|
||||
subscribe: (callback: Function) => {
|
||||
store.addListener('isProfiling', callback);
|
||||
return () => store.removeListener('isProfiling', callback);
|
||||
},
|
||||
}),
|
||||
[store]
|
||||
);
|
||||
const isProfiling = useSubscription<boolean, Store>(isProfilingSubscription);
|
||||
|
||||
const filterPreferencesSubscription = useMemo(
|
||||
() => ({
|
||||
getCurrentValue: () => store.filterPreferences,
|
||||
subscribe: (callback: Function) => {
|
||||
store.addListener('filterPreferences', callback);
|
||||
return () => store.removeListener('filterPreferences', callback);
|
||||
},
|
||||
}),
|
||||
[store]
|
||||
);
|
||||
const filterPreferences = useSubscription<FilterPreferences, Store>(
|
||||
filterPreferencesSubscription
|
||||
);
|
||||
|
||||
const updateFilterPreferences = useCallback(
|
||||
({ currentTarget }) => {
|
||||
const filterPreferences = store.filterPreferences;
|
||||
if (currentTarget.checked) {
|
||||
filterPreferences.hideElementsWithTypes.add(ElementTypeHostComponent);
|
||||
} else {
|
||||
filterPreferences.hideElementsWithTypes.delete(
|
||||
ElementTypeHostComponent
|
||||
);
|
||||
}
|
||||
store.filterPreferences = { ...filterPreferences };
|
||||
},
|
||||
[store]
|
||||
);
|
||||
|
||||
const updateDisplayDensity = useCallback(
|
||||
({ currentTarget }) => {
|
||||
setDisplayDensity(currentTarget.value);
|
||||
@@ -189,18 +143,6 @@ function Settings(_: {||}) {
|
||||
/>{' '}
|
||||
Collapse newly added components by default
|
||||
</label>
|
||||
|
||||
<label className={styles.CheckboxOption}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={filterPreferences.hideElementsWithTypes.has(
|
||||
ElementTypeHostComponent
|
||||
)}
|
||||
disabled={isProfiling}
|
||||
onChange={updateFilterPreferences}
|
||||
/>{' '}
|
||||
Hide host components (e.g. <code><div></code>)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{store.supportsCaptureScreenshots && (
|
||||
|
||||
@@ -247,6 +247,11 @@ function updateThemeVariables(
|
||||
updateStyleHelper(theme, 'color-tab-selected-border', documentElements);
|
||||
updateStyleHelper(theme, 'color-text', documentElements);
|
||||
updateStyleHelper(theme, 'color-text-selected', documentElements);
|
||||
updateStyleHelper(theme, 'color-toggle-background-invalid', documentElements);
|
||||
updateStyleHelper(theme, 'color-toggle-background-on', documentElements);
|
||||
updateStyleHelper(theme, 'color-toggle-background-off', documentElements);
|
||||
updateStyleHelper(theme, 'color-toggle-text', documentElements);
|
||||
updateStyleHelper(theme, 'color-toggle-text', documentElements);
|
||||
updateStyleHelper(theme, 'color-tooltip-background', documentElements);
|
||||
updateStyleHelper(theme, 'color-tooltip-text', documentElements);
|
||||
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
--light-color-tab-selected-border: #0088fa;
|
||||
--light-color-text: #000000;
|
||||
--light-color-text-selected: #ffffff;
|
||||
--light-color-toggle-background-invalid: #fc3a4b;
|
||||
--light-color-toggle-background-on: #0088fa;
|
||||
--light-color-toggle-background-off: #cfd1d5;
|
||||
--light-color-toggle-text: #ffffff;
|
||||
--light-color-tooltip-background: rgba(0, 0, 0, 0.9);
|
||||
--light-color-tooltip-text: #ffffff;
|
||||
|
||||
@@ -96,6 +100,10 @@
|
||||
--dark-color-tab-selected-border: #178fb9;
|
||||
--dark-color-text: #ffffff;
|
||||
--dark-color-text-selected: #ffffff;
|
||||
--dark-color-toggle-background-invalid: #fc3a4b;
|
||||
--dark-color-toggle-background-on: #178fb9;
|
||||
--dark-color-toggle-background-off: #777d88;
|
||||
--dark-color-toggle-text: #ffffff;
|
||||
--dark-color-tooltip-background: rgba(255, 255, 255, 0.9);
|
||||
--dark-color-tooltip-text: #000000;
|
||||
|
||||
|
||||
+28
-10
@@ -13,7 +13,7 @@ export type Wall = {|
|
||||
|};
|
||||
|
||||
// WARNING
|
||||
// The values below are referenced by FilterPreferences (which is saved via localStorage).
|
||||
// The values below are referenced by ComponentFilters (which are saved via localStorage).
|
||||
// Do not change them or it will break previously saved user customizations.
|
||||
// If new element types are added, use new numbers rather than re-ordering existing ones.
|
||||
export const ElementTypeClass = 1;
|
||||
@@ -34,15 +34,33 @@ export const ElementTypeSuspense = 12;
|
||||
// or to enable/disable certain functionality.
|
||||
export type ElementType = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
||||
|
||||
export type FilterPreferences = {|
|
||||
// Hide all elements of types in this Set.
|
||||
// We hide host components only by default.
|
||||
hideElementsWithTypes: Set<ElementType>,
|
||||
// WARNING
|
||||
// The values below are referenced by ComponentFilters (which are saved via localStorage).
|
||||
// Do not change them or it will break previously saved user customizations.
|
||||
// If new filter types are added, use new numbers rather than re-ordering existing ones.
|
||||
export const ComponentFilterElementType = 1;
|
||||
export const ComponentFilterDisplayName = 2;
|
||||
export const ComponentFilterPath = 3;
|
||||
|
||||
// Hide all elements with displayNames matching one or more of the RegExps in this Set.
|
||||
hideElementsWithDisplayNames: Set<RegExp>,
|
||||
export type FilterType = 1 | 2 | 3;
|
||||
|
||||
// Hide all elements within paths matching one or more of the RegExps in this Set.
|
||||
// This filter is only used for elements that include debug source location.
|
||||
hideElementsWithPaths: Set<RegExp>,
|
||||
// Hide all elements of types in this Set.
|
||||
// We hide host components only by default.
|
||||
export type ElementTypeComponentFilter = {|
|
||||
isEnabled: boolean,
|
||||
type: 1,
|
||||
value: ElementType,
|
||||
|};
|
||||
|
||||
// Hide all elements with displayNames or paths matching one or more of the RegExps in this Set.
|
||||
// Path filters are only used when elements include debug source location.
|
||||
export type RegExpComponentFilter = {|
|
||||
isEnabled: boolean,
|
||||
isValid: boolean,
|
||||
type: 2 | 3,
|
||||
value: string,
|
||||
|};
|
||||
|
||||
export type ComponentFilter =
|
||||
| ElementTypeComponentFilter
|
||||
| RegExpComponentFilter;
|
||||
|
||||
+24
-55
@@ -2,9 +2,9 @@
|
||||
|
||||
import LRU from 'lru-cache';
|
||||
import { LOCAL_STORAGE_FILTER_PREFERENCES_KEY } from './constants';
|
||||
import { ElementTypeHostComponent } from './types';
|
||||
import { ComponentFilterElementType, ElementTypeHostComponent } from './types';
|
||||
|
||||
import type { FilterPreferences } from './types';
|
||||
import type { ComponentFilter } from './types';
|
||||
|
||||
const FB_MODULE_RE = /^(.*) \[from (.*)\]$/;
|
||||
const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
|
||||
@@ -81,65 +81,34 @@ function toCodePoint(string: string) {
|
||||
return string.codePointAt(0);
|
||||
}
|
||||
|
||||
export function getDefaultFilterPreferences(): FilterPreferences {
|
||||
return {
|
||||
hideElementsWithTypes: new Set([ElementTypeHostComponent]),
|
||||
hideElementsWithDisplayNames: new Set(),
|
||||
hideElementsWithPaths: new Set(),
|
||||
};
|
||||
// TODO (filters) Save the filters as the frontend needs them (an array, with type and "enabled" status)
|
||||
// Convert the fitlers to Sets for the renderer to consume.
|
||||
|
||||
export function getDefaultComponentFilters(): Array<ComponentFilter> {
|
||||
return [
|
||||
{
|
||||
type: ComponentFilterElementType,
|
||||
value: ElementTypeHostComponent,
|
||||
isEnabled: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getSavedFilterPreferencesFilter(key, value) {
|
||||
if (typeof value === 'string' && value.indexOf('__REGEXP__') === 0) {
|
||||
const match = value.substr(9).match(/\/(.*)\/(.*)?/);
|
||||
return new RegExp(match[1], match[2] || '');
|
||||
}
|
||||
return value;
|
||||
export function getSavedComponentFilters(): Array<ComponentFilter> {
|
||||
try {
|
||||
const raw = localStorage.getItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY);
|
||||
if (raw != null) {
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
} catch (error) {}
|
||||
return getDefaultComponentFilters();
|
||||
}
|
||||
|
||||
export function getSavedFilterPreferences(): FilterPreferences {
|
||||
const raw = localStorage.getItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY);
|
||||
if (raw != null) {
|
||||
const json = JSON.parse(raw, getSavedFilterPreferencesFilter);
|
||||
return {
|
||||
hideElementsWithTypes: new Set(json.hideElementsWithTypes),
|
||||
hideElementsWithDisplayNames: new Set(
|
||||
json.hideElementsWithDisplayNames.map(source => new RegExp(source))
|
||||
),
|
||||
hideElementsWithPaths: new Set(
|
||||
json.hideElementsWithPaths.map(source => new RegExp(source))
|
||||
),
|
||||
};
|
||||
} else {
|
||||
return getDefaultFilterPreferences();
|
||||
}
|
||||
}
|
||||
|
||||
function saveFilterPreferencesFilter(key, value) {
|
||||
if (value instanceof RegExp) {
|
||||
return '__REGEXP__' + value.toString();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function saveFilterPreferences(
|
||||
filterPreferences: FilterPreferences
|
||||
export function saveComponentFilters(
|
||||
componentFilters: Array<ComponentFilter>
|
||||
): void {
|
||||
localStorage.setItem(
|
||||
LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
|
||||
JSON.stringify(
|
||||
{
|
||||
hideElementsWithTypes: Array.from(
|
||||
filterPreferences.hideElementsWithTypes
|
||||
),
|
||||
hideElementsWithDisplayNames: Array.from(
|
||||
filterPreferences.hideElementsWithDisplayNames
|
||||
),
|
||||
hideElementsWithPaths: Array.from(
|
||||
filterPreferences.hideElementsWithPaths
|
||||
),
|
||||
},
|
||||
saveFilterPreferencesFilter
|
||||
)
|
||||
JSON.stringify(componentFilters)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user