Added filter preference types and plugged into renderer partly. Lots of work to do still.
Brian Vaughn committed
Apr 27, 2019 at 10:31 UTC
ee1b38e4dbfdf674ecdb17d3a07a1375ed01979a
10 files changed
+147
-68
src/backend/renderer.js
+16
-44
@@ -14,13 +14,10 @@ import {
14
ElementTypeProfiler,
15
ElementTypeRoot,
16
ElementTypeSuspense,
17
- FilterByElementType,
18
- FilterByName,
19
- FilterByPath,
17
} from 'src/types';
18
import {
19
getDisplayName,
23
- getSavedFilters,
20
+ getSavedFilterPreferences,
21
getUID,
22
utfEncodeString,
23
} from 'src/utils';
@@ -50,7 +47,6 @@ import type {
47
ReactRenderer,
48
RendererInterface,
49
} from './types';
53
-import type { ElementType, Filter } from 'src/types';
50
import type { InspectedElement } from 'src/devtools/views/Components/types';
51
52
function getInternalReactConstants(version) {
@@ -268,35 +264,11 @@ export function attach(
264
}
265
};
266
271
- const filterByElementTypeMap: Map<ElementType, boolean> = new Map();
272
- const filterByNames: Set<RegExp> = new Set();
273
- const filterByPaths: Set<RegExp> = new Set();
274
-
275
- function updateFilters(filters: Array<Filter>): void {
276
- filterByElementTypeMap.clear();
277
- filterByNames.clear();
278
- filterByPaths.clear();
279
-
280
- filters.forEach(({ type, value }) => {
281
- switch (type) {
282
- case FilterByElementType:
283
- filterByElementTypeMap.set(((value: any): ElementType), true);
284
- break;
285
- case FilterByName:
286
- filterByNames.add(((value: any): RegExp));
287
- break;
288
- case FilterByPath:
289
- filterByPaths.add(((value: any): RegExp));
290
- break;
291
- default:
292
- console.error(`Unsupported filter type "${type}"`);
293
- break;
294
- }
295
- });
296
- }
297
-
298
- // Initialize to the persisted values
299
- updateFilters(getSavedFilters());
267
+ const {
268
+ hideElementsWithTypes,
269
+ // TOOD (filter) hideElementsWithDisplayNames,
270
+ // TOOD (filter) hideElementsWithPaths,
271
+ } = getSavedFilterPreferences();
272
273
// NOTICE Keep in sync with getDataForFiber()
274
function shouldFilterFiber(fiber: Fiber): boolean {
@@ -307,21 +279,21 @@ export function attach(
279
switch (tag) {
280
case ClassComponent:
281
case IncompleteClassComponent:
310
- return filterByElementTypeMap.get(ElementTypeClass) === true;
282
+ return hideElementsWithTypes.has(ElementTypeClass);
283
case FunctionComponent:
312
- return filterByElementTypeMap.get(ElementTypeFunction) === true;
284
+ return hideElementsWithTypes.has(ElementTypeFunction);
285
case IndeterminateComponent:
286
return (
315
- filterByElementTypeMap.get(ElementTypeClass) === true ||
316
- filterByElementTypeMap.get(ElementTypeFunction) === true
287
+ hideElementsWithTypes.has(ElementTypeClass) ||
288
+ hideElementsWithTypes.has(ElementTypeFunction)
289
);
290
case ForwardRef:
319
- return filterByElementTypeMap.get(ElementTypeForwardRef) === true;
291
+ return hideElementsWithTypes.has(ElementTypeForwardRef);
292
case MemoComponent:
293
case SimpleMemoComponent:
322
- return filterByElementTypeMap.get(ElementTypeMemo) === true;
294
+ return hideElementsWithTypes.has(ElementTypeMemo);
295
case HostComponent:
324
- return filterByElementTypeMap.get(ElementTypeHostComponent) === true;
296
+ return hideElementsWithTypes.has(ElementTypeHostComponent);
297
case HostRoot:
298
return false; // We never support filtering roots
299
case DehydratedSuspenseComponent:
@@ -350,14 +322,14 @@ export function attach(
322
case CONTEXT_PROVIDER_SYMBOL_STRING:
323
case CONTEXT_CONSUMER_NUMBER:
324
case CONTEXT_CONSUMER_SYMBOL_STRING:
353
- return filterByElementTypeMap.get(ElementTypeContext) === true;
325
+ return hideElementsWithTypes.has(ElementTypeContext);
326
case SUSPENSE_NUMBER:
327
case SUSPENSE_SYMBOL_STRING:
328
case DEPRECATED_PLACEHOLDER_SYMBOL_STRING:
357
- return filterByElementTypeMap.get(ElementTypeSuspense) === true;
329
+ return hideElementsWithTypes.has(ElementTypeSuspense);
330
case PROFILER_NUMBER:
331
case PROFILER_SYMBOL_STRING:
360
- return filterByElementTypeMap.get(ElementTypeProfiler) === true;
332
+ return hideElementsWithTypes.has(ElementTypeProfiler);
333
default:
334
return false;
335
}
src/constants.js
+2
-1
@@ -5,7 +5,8 @@ export const TREE_OPERATION_REMOVE = 2;
5
export const TREE_OPERATION_REORDER_CHILDREN = 3;
6
export const TREE_OPERATION_UPDATE_TREE_BASE_DURATION = 4;
7
8
-export const LOCAL_STORAGE_FILTERS_KEY = 'React::DevTools::filters';
8
+export const LOCAL_STORAGE_FILTER_PREFERENCES_KEY =
9
+ 'React::DevTools::filterPreferences';
10
11
export const LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY =
12
'React::DevTools::reloadAndProfile';
src/devtools/views/ButtonIcon.js
+18
-2
@@ -4,10 +4,12 @@ import React from 'react';
4
import styles from './ButtonIcon.css';
5
6
export type IconType =
7
+ | 'add'
8
| 'cancel'
9
| 'close'
10
| 'collapsed'
11
| 'copy'
12
+ | 'delete'
13
| 'down'
14
| 'expanded'
15
| 'export'
@@ -26,12 +28,16 @@ export type IconType =
28
| 'view-source';
29
30
type Props = {|
31
+ className?: string,
32
type: IconType,
33
|};
34
32
-export default function ButtonIcon({ type }: Props) {
35
+export default function ButtonIcon({ className = '', type }: Props) {
36
let pathData = null;
37
switch (type) {
38
+ case 'add':
39
+ pathData = PATH_ADD;
40
+ break;
41
case 'cancel':
42
pathData = PATH_CANCEL;
43
break;
@@ -44,6 +50,9 @@ export default function ButtonIcon({ type }: Props) {
50
case 'copy':
51
pathData = PATH_COPY;
52
break;
53
+ case 'delete':
54
+ pathData = PATH_DELETE;
55
+ break;
56
case 'down':
57
pathData = PATH_DOWN;
58
break;
@@ -100,7 +109,7 @@ export default function ButtonIcon({ type }: Props) {
109
return (
110
<svg
111
xmlns="http://www.w3.org/2000/svg"
103
- className={styles.ButtonIcon}
112
+ className={`${styles.ButtonIcon} ${className}`}
113
width="24"
114
height="24"
115
viewBox="0 0 24 24"
@@ -111,6 +120,8 @@ export default function ButtonIcon({ type }: Props) {
120
);
121
}
122
123
+const PATH_ADD = 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z';
124
+
125
const PATH_CANCEL = `
126
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
127
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 = `
137
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
138
`;
139
140
+const PATH_DELETE = `
141
+ 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
142
+ 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
143
+`;
144
+
145
const PATH_DOWN = 'M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z';
146
147
const PATH_EXPANDED = 'M7 10l5 5 5-5z';
src/devtools/views/Settings/FilterList.css
new
+4
@@ -0,0 +1,4 @@
1
+.Filter {
2
+ display: block;
3
+ padding: 0.5rem 0;
4
+}
src/devtools/views/Settings/FilterList.js
new
+40
@@ -0,0 +1,40 @@
1
+// @flow
2
+
3
+import React, { Fragment, useCallback, useState } from 'react';
4
+import { getSavedFilterPreferences, saveFilterPreferences } from 'src/utils';
5
+import { ElementTypeHostComponent } from 'src/types';
6
+
7
+import styles from './FilterList.css';
8
+
9
+export default function FilterList(_: {||}) {
10
+ const [filterPreferences, setFilterPreferences] = useState(
11
+ getSavedFilterPreferences
12
+ );
13
+ const updateFilterPreferences = useCallback(() => {
14
+ const clonedFilterPreferences = { ...filterPreferences };
15
+ setFilterPreferences(clonedFilterPreferences);
16
+ saveFilterPreferences(clonedFilterPreferences);
17
+ }, [filterPreferences]);
18
+
19
+ const { hideElementsWithTypes } = filterPreferences;
20
+
21
+ return (
22
+ <Fragment>
23
+ <label className={styles.Filter}>
24
+ <input
25
+ type="checkbox"
26
+ checked={hideElementsWithTypes.has(ElementTypeHostComponent)}
27
+ onChange={() => {
28
+ if (hideElementsWithTypes.has(ElementTypeHostComponent)) {
29
+ hideElementsWithTypes.delete(ElementTypeHostComponent);
30
+ } else {
31
+ hideElementsWithTypes.add(ElementTypeHostComponent);
32
+ }
33
+ updateFilterPreferences();
34
+ }}
35
+ />{' '}
36
+ Hide host components (e.g. <code><div></code>)
37
+ </label>
38
+ </Fragment>
39
+ );
40
+}
src/devtools/views/Settings/Settings.css
+1
@@ -71,6 +71,7 @@
71
}
72
73
.ScreenshotThrottling {
74
+ display: inline-block;
75
background-color: var(--color-background-hover);
76
padding: 0.25rem 0.5rem;
77
border-radius: 0.25rem;
src/devtools/views/Settings/Settings.js
+4
@@ -5,6 +5,7 @@ import { useSubscription } from '../hooks';
5
import { StoreContext } from '../context';
6
import { SettingsContext } from './SettingsContext';
7
import Store from 'src/devtools/store';
8
+import FilterList from './FilterList';
9
import portaledContent from '../portaledContent';
10
11
import styles from './Settings.css';
@@ -134,6 +135,7 @@ function Settings(_: {||}) {
135
136
<div className={styles.Section}>
137
<div className={styles.Header}>Components tree</div>
138
+
139
<label className={styles.CheckboxOption}>
140
<input
141
type="checkbox"
@@ -142,6 +144,8 @@ function Settings(_: {||}) {
144
/>{' '}
145
Collapse newly added components by default
146
</label>
147
+
148
+ <FilterList />
149
</div>
150
151
{store.supportsCaptureScreenshots && (
src/devtools/views/hooks.js
+8
-2
@@ -38,14 +38,20 @@ export function useIsOverflowing(
38
// Forked from https://usehooks.com/useLocalStorage/
39
export function useLocalStorage<T>(
40
key: string,
41
- initialValue: T
41
+ initialValue: T | (() => T)
42
): [T, (value: T | (() => T)) => void] {
43
const getValueFromLocalStorage = useCallback(() => {
44
try {
45
const item = window.localStorage.getItem(key);
46
- return item ? JSON.parse(item) : initialValue;
46
+ if (item != null) {
47
+ return JSON.parse(item);
48
+ }
49
} catch (error) {
50
console.log(error);
51
+ }
52
+ if (typeof initialValue === 'function') {
53
+ return (initialValue: any)();
54
+ } else {
55
return initialValue;
56
}
57
}, [initialValue, key]);
src/types.js
+15
-12
@@ -12,6 +12,10 @@ export type Wall = {|
12
send: (event: string, payload: any, transferable?: Array<any>) => void,
13
|};
14
15
+// WARNING
16
+// The values below are referenced by FilterPreferences (which is saved via localStorage).
17
+// Do not change them or it will break previously saved user customizations.
18
+// If new element types are added, use new numbers rather than re-ordering existing ones.
19
export const ElementTypeClass = 1;
20
export const ElementTypeContext = 2;
21
export const ElementTypeEventComponent = 3;
@@ -30,16 +34,15 @@ export const ElementTypeSuspense = 12;
34
// or to enable/disable certain functionality.
35
export type ElementType = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
36
33
-export const FilterByElementType = 1;
34
-export const FilterByName = 2;
35
-export const FilterByPath = 3;
37
+export type FilterPreferences = {|
38
+ // Hide all elements of types in this Set.
39
+ // We hide host components only by default.
40
+ hideElementsWithTypes: Set<ElementType>,
41
37
-export type Filter =
38
- | {|
39
- type: 1,
40
- value: ElementType,
41
- |}
42
- | {|
43
- type: 2 | 3,
44
- value: RegExp,
45
- |};
42
+ // Hide all elements with displayNames matching one or more of the RegExps in this Set.
43
+ hideElementsWithDisplayNames: Set<RegExp>,
44
+
45
+ // Hide all elements within paths matching one or more of the RegExps in this Set.
46
+ // This filter is only used for elements that include debug source location.
47
+ hideElementsWithPaths: Set<RegExp>,
48
+|};
src/utils.js
+39
-7
@@ -1,10 +1,10 @@
1
// @flow
2
3
import LRU from 'lru-cache';
4
-import { LOCAL_STORAGE_FILTERS_KEY } from './constants';
4
+import { LOCAL_STORAGE_FILTER_PREFERENCES_KEY } from './constants';
5
import { ElementTypeHostComponent } from './types';
6
7
-import type { Filter } from './types';
7
+import type { FilterPreferences } from './types';
8
9
const FB_MODULE_RE = /^(.*) \[from (.*)\]$/;
10
const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
@@ -81,11 +81,43 @@ function toCodePoint(string: string) {
81
return string.codePointAt(0);
82
}
83
84
-export function getSavedFilters(): Array<Filter> {
85
- const filters = localStorage.getItem(LOCAL_STORAGE_FILTERS_KEY);
86
- if (filters != null) {
87
- return ((JSON.parse(filters): any): Array<Filter>);
84
+export function getDefaultFilterPreferences(): FilterPreferences {
85
+ return {
86
+ hideElementsWithTypes: new Set([ElementTypeHostComponent]),
87
+ hideElementsWithDisplayNames: new Set(),
88
+ hideElementsWithPaths: new Set(),
89
+ };
90
+}
91
+
92
+export function getSavedFilterPreferences(): FilterPreferences {
93
+ const raw = localStorage.getItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY);
94
+ if (raw != null) {
95
+ const json = JSON.parse(raw);
96
+ return {
97
+ hideElementsWithTypes: new Set(json.hideElementsWithTypes),
98
+ hideElementsWithDisplayNames: new Set(json.hideElementsWithDisplayNames),
99
+ hideElementsWithPaths: new Set(json.hideElementsWithPaths),
100
+ };
101
} else {
89
- return [{ type: 1, value: ElementTypeHostComponent }];
102
+ return getDefaultFilterPreferences();
103
}
104
}
105
+
106
+export function saveFilterPreferences(
107
+ filterPreferences: FilterPreferences
108
+): void {
109
+ localStorage.setItem(
110
+ LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
111
+ JSON.stringify({
112
+ hideElementsWithTypes: Array.from(
113
+ filterPreferences.hideElementsWithTypes
114
+ ),
115
+ hideElementsWithDisplayNames: Array.from(
116
+ filterPreferences.hideElementsWithDisplayNames
117
+ ),
118
+ hideElementsWithPaths: Array.from(
119
+ filterPreferences.hideElementsWithPaths
120
+ ),
121
+ })
122
+ );
123
+}