Added filter preference types and plugged into renderer partly. Lots of work to do still.

This commit is contained in:
Brian Vaughn 2019-04-27 10:31:40 -07:00
parent 9db209ee64
commit ee1b38e4db
10 changed files with 147 additions and 68 deletions

View File

@ -14,13 +14,10 @@ import {
ElementTypeProfiler,
ElementTypeRoot,
ElementTypeSuspense,
FilterByElementType,
FilterByName,
FilterByPath,
} from 'src/types';
import {
getDisplayName,
getSavedFilters,
getSavedFilterPreferences,
getUID,
utfEncodeString,
} from 'src/utils';
@ -50,7 +47,6 @@ import type {
ReactRenderer,
RendererInterface,
} from './types';
import type { ElementType, Filter } from 'src/types';
import type { InspectedElement } from 'src/devtools/views/Components/types';
function getInternalReactConstants(version) {
@ -268,35 +264,11 @@ export function attach(
}
};
const filterByElementTypeMap: Map<ElementType, boolean> = new Map();
const filterByNames: Set<RegExp> = new Set();
const filterByPaths: Set<RegExp> = new Set();
function updateFilters(filters: Array<Filter>): void {
filterByElementTypeMap.clear();
filterByNames.clear();
filterByPaths.clear();
filters.forEach(({ type, value }) => {
switch (type) {
case FilterByElementType:
filterByElementTypeMap.set(((value: any): ElementType), true);
break;
case FilterByName:
filterByNames.add(((value: any): RegExp));
break;
case FilterByPath:
filterByPaths.add(((value: any): RegExp));
break;
default:
console.error(`Unsupported filter type "${type}"`);
break;
}
});
}
// Initialize to the persisted values
updateFilters(getSavedFilters());
const {
hideElementsWithTypes,
// TOOD (filter) hideElementsWithDisplayNames,
// TOOD (filter) hideElementsWithPaths,
} = getSavedFilterPreferences();
// NOTICE Keep in sync with getDataForFiber()
function shouldFilterFiber(fiber: Fiber): boolean {
@ -307,21 +279,21 @@ export function attach(
switch (tag) {
case ClassComponent:
case IncompleteClassComponent:
return filterByElementTypeMap.get(ElementTypeClass) === true;
return hideElementsWithTypes.has(ElementTypeClass);
case FunctionComponent:
return filterByElementTypeMap.get(ElementTypeFunction) === true;
return hideElementsWithTypes.has(ElementTypeFunction);
case IndeterminateComponent:
return (
filterByElementTypeMap.get(ElementTypeClass) === true ||
filterByElementTypeMap.get(ElementTypeFunction) === true
hideElementsWithTypes.has(ElementTypeClass) ||
hideElementsWithTypes.has(ElementTypeFunction)
);
case ForwardRef:
return filterByElementTypeMap.get(ElementTypeForwardRef) === true;
return hideElementsWithTypes.has(ElementTypeForwardRef);
case MemoComponent:
case SimpleMemoComponent:
return filterByElementTypeMap.get(ElementTypeMemo) === true;
return hideElementsWithTypes.has(ElementTypeMemo);
case HostComponent:
return filterByElementTypeMap.get(ElementTypeHostComponent) === true;
return hideElementsWithTypes.has(ElementTypeHostComponent);
case HostRoot:
return false; // We never support filtering roots
case DehydratedSuspenseComponent:
@ -350,14 +322,14 @@ export function attach(
case CONTEXT_PROVIDER_SYMBOL_STRING:
case CONTEXT_CONSUMER_NUMBER:
case CONTEXT_CONSUMER_SYMBOL_STRING:
return filterByElementTypeMap.get(ElementTypeContext) === true;
return hideElementsWithTypes.has(ElementTypeContext);
case SUSPENSE_NUMBER:
case SUSPENSE_SYMBOL_STRING:
case DEPRECATED_PLACEHOLDER_SYMBOL_STRING:
return filterByElementTypeMap.get(ElementTypeSuspense) === true;
return hideElementsWithTypes.has(ElementTypeSuspense);
case PROFILER_NUMBER:
case PROFILER_SYMBOL_STRING:
return filterByElementTypeMap.get(ElementTypeProfiler) === true;
return hideElementsWithTypes.has(ElementTypeProfiler);
default:
return false;
}

View File

@ -5,7 +5,8 @@ export const TREE_OPERATION_REMOVE = 2;
export const TREE_OPERATION_REORDER_CHILDREN = 3;
export const TREE_OPERATION_UPDATE_TREE_BASE_DURATION = 4;
export const LOCAL_STORAGE_FILTERS_KEY = 'React::DevTools::filters';
export const LOCAL_STORAGE_FILTER_PREFERENCES_KEY =
'React::DevTools::filterPreferences';
export const LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY =
'React::DevTools::reloadAndProfile';

View File

@ -4,10 +4,12 @@ import React from 'react';
import styles from './ButtonIcon.css';
export type IconType =
| 'add'
| 'cancel'
| 'close'
| 'collapsed'
| 'copy'
| 'delete'
| 'down'
| 'expanded'
| 'export'
@ -26,12 +28,16 @@ export type IconType =
| 'view-source';
type Props = {|
className?: string,
type: IconType,
|};
export default function ButtonIcon({ type }: Props) {
export default function ButtonIcon({ className = '', type }: Props) {
let pathData = null;
switch (type) {
case 'add':
pathData = PATH_ADD;
break;
case 'cancel':
pathData = PATH_CANCEL;
break;
@ -44,6 +50,9 @@ export default function ButtonIcon({ type }: Props) {
case 'copy':
pathData = PATH_COPY;
break;
case 'delete':
pathData = PATH_DELETE;
break;
case 'down':
pathData = PATH_DOWN;
break;
@ -100,7 +109,7 @@ export default function ButtonIcon({ type }: Props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={styles.ButtonIcon}
className={`${styles.ButtonIcon} ${className}`}
width="24"
height="24"
viewBox="0 0 24 24"
@ -111,6 +120,8 @@ export default function ButtonIcon({ type }: Props) {
);
}
const PATH_ADD = 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z';
const PATH_CANCEL = `
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
@ -126,6 +137,11 @@ const PATH_COPY = `
2v10a2 2 0 0 0 2 2h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 12H9V5h10v10zm-8 6h2v-2h-2v2zm-4 0h2v-2H7v2z
`;
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
`;
const PATH_DOWN = 'M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z';
const PATH_EXPANDED = 'M7 10l5 5 5-5z';

View File

@ -0,0 +1,4 @@
.Filter {
display: block;
padding: 0.5rem 0;
}

View File

@ -0,0 +1,40 @@
// @flow
import React, { Fragment, useCallback, useState } from 'react';
import { getSavedFilterPreferences, saveFilterPreferences } from 'src/utils';
import { ElementTypeHostComponent } from 'src/types';
import styles from './FilterList.css';
export default function FilterList(_: {||}) {
const [filterPreferences, setFilterPreferences] = useState(
getSavedFilterPreferences
);
const updateFilterPreferences = useCallback(() => {
const clonedFilterPreferences = { ...filterPreferences };
setFilterPreferences(clonedFilterPreferences);
saveFilterPreferences(clonedFilterPreferences);
}, [filterPreferences]);
const { hideElementsWithTypes } = filterPreferences;
return (
<Fragment>
<label className={styles.Filter}>
<input
type="checkbox"
checked={hideElementsWithTypes.has(ElementTypeHostComponent)}
onChange={() => {
if (hideElementsWithTypes.has(ElementTypeHostComponent)) {
hideElementsWithTypes.delete(ElementTypeHostComponent);
} else {
hideElementsWithTypes.add(ElementTypeHostComponent);
}
updateFilterPreferences();
}}
/>{' '}
Hide host components (e.g. <code>&lt;div&gt;</code>)
</label>
</Fragment>
);
}

View File

@ -71,6 +71,7 @@
}
.ScreenshotThrottling {
display: inline-block;
background-color: var(--color-background-hover);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;

View File

@ -5,6 +5,7 @@ import { useSubscription } from '../hooks';
import { StoreContext } from '../context';
import { SettingsContext } from './SettingsContext';
import Store from 'src/devtools/store';
import FilterList from './FilterList';
import portaledContent from '../portaledContent';
import styles from './Settings.css';
@ -134,6 +135,7 @@ function Settings(_: {||}) {
<div className={styles.Section}>
<div className={styles.Header}>Components tree</div>
<label className={styles.CheckboxOption}>
<input
type="checkbox"
@ -142,6 +144,8 @@ function Settings(_: {||}) {
/>{' '}
Collapse newly added components by default
</label>
<FilterList />
</div>
{store.supportsCaptureScreenshots && (

View File

@ -38,14 +38,20 @@ export function useIsOverflowing(
// Forked from https://usehooks.com/useLocalStorage/
export function useLocalStorage<T>(
key: string,
initialValue: T
initialValue: T | (() => T)
): [T, (value: T | (() => T)) => void] {
const getValueFromLocalStorage = useCallback(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
if (item != null) {
return JSON.parse(item);
}
} catch (error) {
console.log(error);
}
if (typeof initialValue === 'function') {
return (initialValue: any)();
} else {
return initialValue;
}
}, [initialValue, key]);

View File

@ -12,6 +12,10 @@ export type Wall = {|
send: (event: string, payload: any, transferable?: Array<any>) => void,
|};
// WARNING
// The values below are referenced by FilterPreferences (which is 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;
export const ElementTypeContext = 2;
export const ElementTypeEventComponent = 3;
@ -30,16 +34,15 @@ 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 const FilterByElementType = 1;
export const FilterByName = 2;
export const FilterByPath = 3;
export type FilterPreferences = {|
// Hide all elements of types in this Set.
// We hide host components only by default.
hideElementsWithTypes: Set<ElementType>,
export type Filter =
| {|
type: 1,
value: ElementType,
|}
| {|
type: 2 | 3,
value: RegExp,
|};
// Hide all elements with displayNames matching one or more of the RegExps in this Set.
hideElementsWithDisplayNames: Set<RegExp>,
// 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>,
|};

View File

@ -1,10 +1,10 @@
// @flow
import LRU from 'lru-cache';
import { LOCAL_STORAGE_FILTERS_KEY } from './constants';
import { LOCAL_STORAGE_FILTER_PREFERENCES_KEY } from './constants';
import { ElementTypeHostComponent } from './types';
import type { Filter } from './types';
import type { FilterPreferences } from './types';
const FB_MODULE_RE = /^(.*) \[from (.*)\]$/;
const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
@ -81,11 +81,43 @@ function toCodePoint(string: string) {
return string.codePointAt(0);
}
export function getSavedFilters(): Array<Filter> {
const filters = localStorage.getItem(LOCAL_STORAGE_FILTERS_KEY);
if (filters != null) {
return ((JSON.parse(filters): any): Array<Filter>);
export function getDefaultFilterPreferences(): FilterPreferences {
return {
hideElementsWithTypes: new Set([ElementTypeHostComponent]),
hideElementsWithDisplayNames: new Set(),
hideElementsWithPaths: new Set(),
};
}
export function getSavedFilterPreferences(): FilterPreferences {
const raw = localStorage.getItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY);
if (raw != null) {
const json = JSON.parse(raw);
return {
hideElementsWithTypes: new Set(json.hideElementsWithTypes),
hideElementsWithDisplayNames: new Set(json.hideElementsWithDisplayNames),
hideElementsWithPaths: new Set(json.hideElementsWithPaths),
};
} else {
return [{ type: 1, value: ElementTypeHostComponent }];
return getDefaultFilterPreferences();
}
}
export function saveFilterPreferences(
filterPreferences: FilterPreferences
): 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
),
})
);
}