Added filter UI (but with a lot of $FlowFixMe comments)
Brian Vaughn committed
May 1, 2019 at 10:43 UTC
84292201644143dd3f2aace2753f10caec339840
19 files changed
+831
-196
src/backend/agent.js
+4
-4
@@ -16,7 +16,7 @@ import type {
16
RendererID,
17
RendererInterface,
18
} from './types';
19
-import type { Bridge, FilterPreferences } from '../types';
19
+import type { Bridge, ComponentFilter } from '../types';
20
21
const debug = (methodName, ...args) => {
22
if (__DEBUG__) {
@@ -118,7 +118,7 @@ export default class Agent extends EventEmitter {
118
this.syncSelectionFromNativeElementsPanel
119
);
120
bridge.addListener('shutdown', this.shutdown);
121
- bridge.addListener('updateFilterPreferences', this.updateFilterPreferences);
121
+ bridge.addListener('updateComponentFilters', this.updateComponentFilters);
122
bridge.addListener('viewElementSource', this.viewElementSource);
123
124
if (this._isProfiling) {
@@ -490,12 +490,12 @@ export default class Agent extends EventEmitter {
490
this._bridge.send('profilingStatus', this._isProfiling);
491
};
492
493
- updateFilterPreferences = (filterPreferences: FilterPreferences) => {
493
+ updateComponentFilters = (componentFilters: Array<ComponentFilter>) => {
494
for (let rendererID in this._rendererInterfaces) {
495
const renderer = ((this._rendererInterfaces[
496
(rendererID: any)
497
]: any): RendererInterface);
498
- renderer.updateFilterPreferences(filterPreferences);
498
+ renderer.updateComponentFilters(componentFilters);
499
}
500
};
501
src/backend/renderer.js
+49
-13
@@ -2,6 +2,9 @@
2
3
import { gte } from 'semver';
4
import {
5
+ ComponentFilterDisplayName,
6
+ ComponentFilterElementType,
7
+ ComponentFilterPath,
8
ElementTypeClass,
9
ElementTypeContext,
10
ElementTypeEventComponent,
@@ -17,7 +20,7 @@ import {
20
} from 'src/types';
21
import {
22
getDisplayName,
20
- getSavedFilterPreferences,
23
+ getSavedComponentFilters,
24
getUID,
25
utfEncodeString,
26
} from 'src/utils';
@@ -47,7 +50,7 @@ import type {
50
RendererInterface,
51
} from './types';
52
import type { InspectedElement } from 'src/devtools/views/Components/types';
50
-import type { ElementType, FilterPreferences } from 'src/types';
53
+import type { ComponentFilter, ElementType } from 'src/types';
54
55
function getInternalReactConstants(version) {
56
const ReactSymbols = {
@@ -261,17 +264,53 @@ export function attach(
264
}
265
};
266
264
- let {
265
- hideElementsWithTypes,
266
- hideElementsWithDisplayNames,
267
- hideElementsWithPaths,
268
- } = getSavedFilterPreferences();
267
+ // Configurable Components tree filters.
268
+ const hideElementsWithDisplayNames: Set<RegExp> = new Set();
269
+ const hideElementsWithPaths: Set<RegExp> = new Set();
270
+ const hideElementsWithTypes: Set<ElementType> = new Set();
271
+
272
+ function applyComponentFilters(componentFilters: Array<ComponentFilter>) {
273
+ hideElementsWithTypes.clear();
274
+ hideElementsWithDisplayNames.clear();
275
+ hideElementsWithPaths.clear();
276
+
277
+ componentFilters.forEach(componentFilter => {
278
+ if (!componentFilter.isEnabled) {
279
+ return;
280
+ }
281
+
282
+ switch (componentFilter.type) {
283
+ case ComponentFilterDisplayName:
284
+ if (componentFilter.isValid && componentFilter.value !== '') {
285
+ hideElementsWithDisplayNames.add(
286
+ new RegExp(componentFilter.value, 'i')
287
+ );
288
+ }
289
+ break;
290
+ case ComponentFilterElementType:
291
+ hideElementsWithTypes.add(componentFilter.value);
292
+ break;
293
+ case ComponentFilterPath:
294
+ if (componentFilter.isValid && componentFilter.value !== '') {
295
+ hideElementsWithPaths.add(new RegExp(componentFilter.value, 'i'));
296
+ }
297
+ break;
298
+ default:
299
+ console.warn(
300
+ `Invalid component filter type "${componentFilter.type}"`
301
+ );
302
+ break;
303
+ }
304
+ });
305
+ }
306
+
307
+ applyComponentFilters(getSavedComponentFilters());
308
309
// TODO (filter) Should we make this operation more efficient?
310
// For example, we could add a new recursive unmount tree operation.
311
// The unmount operations are already significantly smaller than mount opreations though.
312
// This is something to keep in mind for later.
274
- function updateFilterPreferences(filterPreferences: FilterPreferences) {
313
+ function updateComponentFilters(componentFilters: Array<ComponentFilter>) {
314
if (this._isProfiling) {
315
// Re-mounting a tree while profiling is in progress might break a lot of assumptions.
316
// If necessary, we could support this- but it doesn't seem like a necessary use case.
@@ -286,10 +325,7 @@ export function attach(
325
currentRootID = -1;
326
});
327
289
- hideElementsWithTypes = filterPreferences.hideElementsWithTypes;
290
- hideElementsWithDisplayNames =
291
- filterPreferences.hideElementsWithDisplayNames;
292
- hideElementsWithPaths = filterPreferences.hideElementsWithPaths;
328
+ applyComponentFilters(componentFilters);
329
330
// Recursively re-mount all roots with new filter criteria applied.
331
hook.getFiberRoots(rendererID).forEach(root => {
@@ -2280,6 +2316,6 @@ export function attach(
2316
setTrackedPath,
2317
startProfiling,
2318
stopProfiling,
2283
- updateFilterPreferences,
2319
+ updateComponentFilters,
2320
};
2321
}
src/backend/types.js
+2
-2
@@ -1,6 +1,6 @@
1
// @flow
2
3
-import type { ElementType, FilterPreferences } from 'src/types';
3
+import type { ComponentFilter, ElementType } from 'src/types';
4
import type { InspectedElement } from 'src/devtools/views/Components/types';
5
6
type BundleType =
@@ -139,7 +139,7 @@ export type RendererInterface = {
139
setTrackedPath: (path: Array<PathFrame> | null) => void,
140
startProfiling: () => void,
141
stopProfiling: () => void,
142
- updateFilterPreferences: (filterPreferences: FilterPreferences) => void,
142
+ updateComponentFilters: (somponentFilters: Array<ComponentFilter>) => void,
143
};
144
145
export type Handler = (data: any) => void;
src/constants.js
+1
-1
@@ -6,7 +6,7 @@ export const TREE_OPERATION_REORDER_CHILDREN = 3;
6
export const TREE_OPERATION_UPDATE_TREE_BASE_DURATION = 4;
7
8
export const LOCAL_STORAGE_FILTER_PREFERENCES_KEY =
9
- 'React::DevTools::filterPreferences';
9
+ 'React::DevTools::componentFilters';
10
11
export const LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY =
12
'React::DevTools::reloadAndProfile';
src/devtools/store.js
+12
-12
@@ -11,8 +11,8 @@ import {
11
} from '../constants';
12
import { ElementTypeRoot } from '../types';
13
import {
14
- getSavedFilterPreferences,
15
- saveFilterPreferences,
14
+ getSavedComponentFilters,
15
+ saveComponentFilters,
16
utfDecodeString,
17
} from '../utils';
18
import { __DEBUG__ } from '../constants';
@@ -24,7 +24,7 @@ import type {
24
ImportedProfilingData,
25
ProfilingSnapshotNode,
26
} from './views/Profiler/types';
27
-import type { Bridge, ElementType, FilterPreferences } from '../types';
27
+import type { Bridge, ComponentFilter, ElementType } from '../types';
28
29
const debug = (methodName, ...args) => {
30
if (__DEBUG__) {
@@ -69,7 +69,7 @@ export default class Store extends EventEmitter {
69
// Should new nodes be collapsed by default when added to the tree?
70
_collapseNodesByDefault: boolean = true;
71
72
- _filterPreferences: FilterPreferences;
72
+ _componentFilters: Array<ComponentFilter>;
73
74
// At least one of the injected renderers contains (DEV only) owner metadata.
75
_hasOwnerMetadata: boolean = false;
@@ -143,7 +143,7 @@ export default class Store extends EventEmitter {
143
localStorage.getItem(LOCAL_STORAGE_COLLAPSE_ROOTS_BY_DEFAULT_KEY) !==
144
'false';
145
146
- this._filterPreferences = getSavedFilterPreferences();
146
+ this._componentFilters = getSavedComponentFilters();
147
148
if (config != null) {
149
const {
@@ -239,26 +239,26 @@ export default class Store extends EventEmitter {
239
this.emit('collapseNodesByDefault');
240
}
241
242
- get filterPreferences(): FilterPreferences {
243
- return this._filterPreferences;
242
+ get componentFilters(): Array<ComponentFilter> {
243
+ return this._componentFilters;
244
}
245
- set filterPreferences(value: FilterPreferences): void {
245
+ set componentFilters(value: Array<ComponentFilter>): void {
246
if (this._isProfiling) {
247
// Re-mounting a tree while profiling is in progress might break a lot of assumptions.
248
// If necessary, we could support this- but it doesn't seem like a necessary use case.
249
throw Error('Cannot modify filter preferences while profiling');
250
}
251
252
- this._filterPreferences = value;
252
+ this._componentFilters = value;
253
254
// Update persisted filter preferences stored in localStorage.
255
- saveFilterPreferences(value);
255
+ saveComponentFilters(value);
256
257
// Notify the renderer that filter prefernces have changed.
258
// This is an expensive opreation; it unmounts and remounts the entire tree.
259
- this._bridge.send('updateFilterPreferences', value);
259
+ this._bridge.send('updateComponentFilters', value);
260
261
- this.emit('filterPreferences');
261
+ this.emit('componentFilters');
262
}
263
264
get hasOwnerMetadata(): boolean {
src/devtools/views/Button.js
+2
-2
@@ -9,13 +9,13 @@ import tooltipStyles from './Tooltip.css';
9
type Props = {
10
children: React$Node,
11
className?: string,
12
- title: string,
12
+ title?: string,
13
};
14
15
export default function Button({
16
children,
17
className = '',
18
- title,
18
+ title = '',
19
...rest
20
}: Props) {
21
let button = (
src/devtools/views/ButtonIcon.js
+47
-3
@@ -6,6 +6,7 @@ import styles from './ButtonIcon.css';
6
export type IconType =
7
| 'add'
8
| 'cancel'
9
+ | 'clear'
10
| 'close'
11
| 'collapsed'
12
| 'copy'
@@ -21,7 +22,11 @@ export type IconType =
22
| 'previous'
23
| 'record'
24
| 'reload'
25
+ | 'save'
26
| 'search'
27
+ | 'settings'
28
+ | 'toggle_off'
29
+ | 'toggle_on'
30
| 'undo'
31
| 'up'
32
| 'view-dom'
@@ -41,6 +46,9 @@ export default function ButtonIcon({ className = '', type }: Props) {
46
case 'cancel':
47
pathData = PATH_CANCEL;
48
break;
49
+ case 'clear':
50
+ pathData = PATH_CLEAR;
51
+ break;
52
case 'close':
53
pathData = PATH_CLOSE;
54
break;
@@ -86,9 +94,21 @@ export default function ButtonIcon({ className = '', type }: Props) {
94
case 'reload':
95
pathData = PATH_RELOAD;
96
break;
97
+ case 'save':
98
+ pathData = PATH_SAVE;
99
+ break;
100
case 'search':
101
pathData = PATH_SEARCH;
102
break;
103
+ case 'settings':
104
+ pathData = PATH_SETTINGS;
105
+ break;
106
+ case 'toggle_off':
107
+ pathData = PATH_TOGGLE_OFF;
108
+ break;
109
+ case 'toggle_on':
110
+ pathData = PATH_TOGGLE_ON;
111
+ break;
112
case 'undo':
113
pathData = PATH_UNDO;
114
break;
@@ -120,9 +140,14 @@ export default function ButtonIcon({ className = '', type }: Props) {
140
);
141
}
142
123
-const PATH_ADD = 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z';
143
+const PATH_ADD =
144
+ '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';
145
146
const PATH_CANCEL = `
147
+ 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
148
+`;
149
+
150
+const PATH_CLEAR = `
151
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
152
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
153
`;
@@ -138,8 +163,8 @@ const PATH_COPY = `
163
`;
164
165
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
166
+ 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
167
+ 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
168
`;
169
170
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 = `
202
3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z
203
`;
204
205
+const PATH_SAVE = `
206
+ 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
207
+`;
208
+
209
const PATH_SEARCH = `
210
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
211
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
212
`;
213
214
+const PATH_SETTINGS = `
215
+ 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
216
+ 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
217
+ 0-.49.18l-1.6 2.77c-.1.18-.06.39.1.51l1.69
218
+ 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
219
+ 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
220
+ .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
221
+`;
222
+
223
+const PATH_TOGGLE_OFF =
224
+ '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';
225
+
226
+const PATH_TOGGLE_ON =
227
+ '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';
228
+
229
const PATH_UNDO = `
230
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
231
3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z
src/devtools/views/Components/ComponentFiltersModal.css
new
+119
@@ -0,0 +1,119 @@
1
+.Background {
2
+ position: absolute;
3
+ width: 100%;
4
+ height: 100%;
5
+ display: flex;
6
+ align-items: flex-start;
7
+ justify-content: center;
8
+ padding: 1rem;
9
+ background-color: var(--color-modal-background);
10
+ overflow: auto;
11
+}
12
+
13
+.Modal {
14
+ position: relative;
15
+ z-index: 3;
16
+ min-width: 20rem;
17
+ max-width: 100%;
18
+ display: inline-block;
19
+ background-color: var(--color-background);
20
+ padding: 0.5rem;
21
+ border: 1px solid var(--color-border);
22
+ border-radius: 0.25rem;
23
+}
24
+
25
+.LeftRight {
26
+ display: flex;
27
+}
28
+.Left {
29
+}
30
+.Right {
31
+ flex: 1;
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: flex-end;
35
+}
36
+
37
+.ButtonIcon {
38
+ margin-right: 0.25rem;
39
+}
40
+
41
+.NoFiltersCell {
42
+ padding: 0.25rem 0;
43
+ color: var(--color-dim);
44
+}
45
+
46
+.Table {
47
+ min-width: 20rem;
48
+ margin-top: 0.5rem;
49
+ border-spacing: 0;
50
+}
51
+
52
+.TableRow {
53
+ padding-bottom: 0.5rem;
54
+}
55
+
56
+.TableCell {
57
+ padding: 0;
58
+ padding-right: 0.5rem;
59
+}
60
+.TableCell:last-of-type {
61
+ text-align: right;
62
+ padding-right: 0;
63
+}
64
+
65
+.Select {
66
+}
67
+
68
+.Input {
69
+ border: none;
70
+ border-radius: 0.125rem;
71
+ padding: 0.125rem;
72
+}
73
+
74
+.CancelButton {
75
+ margin-right: 0.25rem;
76
+}
77
+
78
+.InvalidRegExp,
79
+.InvalidRegExp:active,
80
+.InvalidRegExp:focus,
81
+.InvalidRegExp:hover {
82
+ color: var(--color-value-invalid);
83
+}
84
+
85
+.ToggleOffInvalid,
86
+.ToggleOnInvalid,
87
+.ToggleOff,
88
+.ToggleOn {
89
+ border-radius: 0.75rem;
90
+ width: 1rem;
91
+ height: 0.625rem;
92
+ display: flex;
93
+ align-items: center;
94
+ padding: 0.125rem;
95
+}
96
+.ToggleOffInvalid {
97
+ background-color: var(--color-toggle-background-invalid);
98
+ justify-content: flex-start;
99
+}
100
+.ToggleOnInvalid {
101
+ background-color: var(--color-toggle-background-invalid);
102
+ justify-content: flex-end;
103
+}
104
+.ToggleOff {
105
+ background-color: var(--color-toggle-background-off);
106
+ justify-content: flex-start;
107
+}
108
+.ToggleOn {
109
+ background-color: var(--color-toggle-background-on);
110
+ justify-content: flex-end;
111
+}
112
+
113
+.ToggleInsideOff,
114
+.ToggleInsideOn {
115
+ border-radius: 0.375rem;
116
+ width: 0.375rem;
117
+ height: 0.375rem;
118
+ background-color: var(--color-toggle-text);
119
+}
src/devtools/views/Components/ComponentFiltersModal.js
new
+406
@@ -0,0 +1,406 @@
1
+// @flow
2
+
3
+import React, {
4
+ useCallback,
5
+ useContext,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+} from 'react';
10
+import { useModalDismissSignal, useSubscription } from '../hooks';
11
+import { ComponentFiltersModalContext } from './ComponentFiltersModalContext';
12
+import { StoreContext } from '../context';
13
+import Button from '../Button';
14
+import ButtonIcon from '../ButtonIcon';
15
+import Toggle from '../Toggle';
16
+import Store from 'src/devtools/store';
17
+import {
18
+ ComponentFilterElementType,
19
+ ComponentFilterDisplayName,
20
+ ComponentFilterPath,
21
+ ElementTypeClass,
22
+ ElementTypeContext,
23
+ ElementTypeEventTarget,
24
+ ElementTypeFunction,
25
+ ElementTypeForwardRef,
26
+ ElementTypeHostComponent,
27
+ ElementTypeMemo,
28
+ ElementTypeOtherOrUnknown,
29
+ ElementTypeProfiler,
30
+ ElementTypeSuspense,
31
+} from 'src/types';
32
+import styles from './ComponentFiltersModal.css';
33
+
34
+import type {
35
+ ComponentFilter,
36
+ ElementType,
37
+ ElementTypeComponentFilter,
38
+ FilterType,
39
+ RegExpComponentFilter,
40
+} from 'src/types';
41
+
42
+export default function ComponentFiltersModalWrapper(_: {||}) {
43
+ const store = useContext(StoreContext);
44
+
45
+ const { isModalShowing, setIsModalShowing } = useContext(
46
+ ComponentFiltersModalContext
47
+ );
48
+
49
+ // Re-mounting a tree while profiling is in progress might break a lot of assumptions.
50
+ // If necessary, we could support this- but it doesn't seem like a necessary use case.
51
+ const isProfilingSubscription = useMemo(
52
+ () => ({
53
+ getCurrentValue: () => store.isProfiling,
54
+ subscribe: (callback: Function) => {
55
+ store.addListener('isProfiling', callback);
56
+ return () => store.removeListener('isProfiling', callback);
57
+ },
58
+ }),
59
+ [store]
60
+ );
61
+ const isProfiling = useSubscription<boolean, Store>(isProfilingSubscription);
62
+ if (isProfiling && isModalShowing) {
63
+ setIsModalShowing(false);
64
+ }
65
+
66
+ return isModalShowing ? (
67
+ <ComponentFiltersModal
68
+ store={store}
69
+ setIsModalShowing={setIsModalShowing}
70
+ />
71
+ ) : null;
72
+}
73
+
74
+type Props = {|
75
+ store: Store,
76
+ setIsModalShowing: (value: boolean) => void,
77
+|};
78
+
79
+function ComponentFiltersModal({ store, setIsModalShowing }: Props) {
80
+ const dismissModal = useCallback(() => setIsModalShowing(false), [
81
+ setIsModalShowing,
82
+ ]);
83
+
84
+ const modalRef = useRef<HTMLDivElement | null>(null);
85
+
86
+ useModalDismissSignal(modalRef, dismissModal);
87
+
88
+ const {
89
+ addFilter,
90
+ changeFilterType,
91
+ updateFilterValueElementType,
92
+ updateFilterValueRegExp,
93
+ componentFilters,
94
+ removeFilter,
95
+ saveFilters,
96
+ toggleFilterIsEnabled,
97
+ } = useComponentFilters();
98
+
99
+ const saveAndClose = useCallback(() => {
100
+ saveFilters();
101
+ dismissModal();
102
+ }, [dismissModal, saveFilters]);
103
+
104
+ return (
105
+ <div className={styles.Background}>
106
+ <div className={styles.Modal} ref={modalRef}>
107
+ <div className={styles.LeftRight}>
108
+ <div className={styles.Left}>Hide components where...</div>
109
+ <div className={styles.Right}>
110
+ <Button onClick={addFilter}>
111
+ <ButtonIcon className={styles.ButtonIcon} type="add" />
112
+ Add filter
113
+ </Button>
114
+ </div>
115
+ </div>
116
+ <table className={styles.Table}>
117
+ <tbody>
118
+ {componentFilters.length === 0 && (
119
+ <tr className={styles.TableRow}>
120
+ <td className={styles.NoFiltersCell}>
121
+ No filters have been added.
122
+ </td>
123
+ </tr>
124
+ )}
125
+ {componentFilters.map((componentFilter, index) => (
126
+ <tr className={styles.TableRow} key={index}>
127
+ <td className={styles.TableCell}>
128
+ <Toggle
129
+ className={
130
+ componentFilter.isValid !== false
131
+ ? ''
132
+ : styles.InvalidRegExp
133
+ }
134
+ isChecked={componentFilter.isEnabled}
135
+ onChange={isEnabled =>
136
+ toggleFilterIsEnabled(componentFilter, isEnabled)
137
+ }
138
+ title={
139
+ componentFilter.isValid === false
140
+ ? 'Filter invalid'
141
+ : componentFilter.isEnabled
142
+ ? 'Filter enabled'
143
+ : 'Filter disabled'
144
+ }
145
+ >
146
+ <ToggleIcon
147
+ isEnabled={componentFilter.isEnabled}
148
+ isValid={
149
+ componentFilter.isValid == null ||
150
+ componentFilter.isValid === true
151
+ }
152
+ />
153
+ </Toggle>
154
+ </td>
155
+ <td className={styles.TableCell}>
156
+ <select
157
+ className={styles.Select}
158
+ value={componentFilter.type}
159
+ onChange={({ currentTarget }) =>
160
+ changeFilterType(
161
+ componentFilter,
162
+ // $FlowFixMe TODO (filters)
163
+ parseInt(currentTarget.value, 10)
164
+ )
165
+ }
166
+ >
167
+ <option value={ComponentFilterElementType}>type</option>
168
+ <option value={ComponentFilterDisplayName}>name</option>
169
+ <option value={ComponentFilterPath}>path</option>
170
+ </select>
171
+ </td>
172
+ <td className={styles.TableCell}>
173
+ {componentFilter.type === ComponentFilterElementType
174
+ ? 'equals'
175
+ : 'matches'}
176
+ </td>
177
+ <td className={styles.TableCell}>
178
+ {componentFilter.type === ComponentFilterElementType ? (
179
+ <select
180
+ className={styles.Select}
181
+ value={componentFilter.value}
182
+ onChange={({ currentTarget }) =>
183
+ updateFilterValueElementType(
184
+ componentFilter,
185
+ // $FlowFixMe TODO (filters)
186
+ parseInt(currentTarget.value, 10)
187
+ )
188
+ }
189
+ >
190
+ <option value={ElementTypeClass}>class</option>
191
+ <option value={ElementTypeContext}>context</option>
192
+ <option value={ElementTypeEventTarget}>event</option>
193
+ <option value={ElementTypeFunction}>function</option>
194
+ <option value={ElementTypeForwardRef}>forward ref</option>
195
+ <option value={ElementTypeHostComponent}>
196
+ host (e.g. <div>)
197
+ </option>
198
+ <option value={ElementTypeMemo}>memo</option>
199
+ <option value={ElementTypeOtherOrUnknown}>other</option>
200
+ <option value={ElementTypeProfiler}>profiler</option>
201
+ <option value={ElementTypeSuspense}>suspense</option>
202
+ </select>
203
+ ) : (
204
+ <input
205
+ className={styles.Input}
206
+ type="text"
207
+ placeholder="Regular expression"
208
+ onChange={({ currentTarget }) =>
209
+ updateFilterValueRegExp(
210
+ componentFilter,
211
+ currentTarget.value
212
+ )
213
+ }
214
+ value={componentFilter.value}
215
+ />
216
+ )}
217
+ </td>
218
+ <td className={styles.TableCell}>
219
+ <Button
220
+ onClick={() => removeFilter(index)}
221
+ title="Delete filter"
222
+ >
223
+ <ButtonIcon type="delete" />
224
+ </Button>
225
+ </td>
226
+ </tr>
227
+ ))}
228
+ </tbody>
229
+ </table>
230
+ <div className={styles.LeftRight}>
231
+ <div className={styles.Right}>
232
+ <Button className={styles.CancelButton} onClick={dismissModal}>
233
+ Cancel
234
+ </Button>
235
+ <Button onClick={saveAndClose}>Save Changes</Button>
236
+ </div>
237
+ </div>
238
+ </div>
239
+ </div>
240
+ );
241
+}
242
+
243
+type ToggleIconProps = {|
244
+ isEnabled: boolean,
245
+ isValid: boolean,
246
+|};
247
+function ToggleIcon({ isEnabled, isValid }: ToggleIconProps) {
248
+ let className;
249
+ if (isValid) {
250
+ className = isEnabled ? styles.ToggleOn : styles.ToggleOff;
251
+ } else {
252
+ className = isEnabled ? styles.ToggleOnInvalid : styles.ToggleOffInvalid;
253
+ }
254
+ return (
255
+ <div className={className}>
256
+ <div
257
+ className={isEnabled ? styles.ToggleInsideOn : styles.ToggleInsideOff}
258
+ />
259
+ </div>
260
+ );
261
+}
262
+
263
+function useComponentFilters() {
264
+ const store = useContext(StoreContext);
265
+
266
+ const [componentFilters, setComponentFilters] = useState<
267
+ Array<ComponentFilter>
268
+ >(() => [...store.componentFilters]);
269
+
270
+ const addFilter = useCallback(() => {
271
+ setComponentFilters(componentFilters => {
272
+ return [
273
+ ...componentFilters,
274
+ {
275
+ type: ComponentFilterElementType,
276
+ value: ElementTypeHostComponent,
277
+ isEnabled: true,
278
+ },
279
+ ];
280
+ });
281
+ }, []);
282
+
283
+ const changeFilterType = useCallback(
284
+ (componentFilter: ComponentFilter, type: FilterType) => {
285
+ setComponentFilters(componentFilters => {
286
+ const cloned: Array<ComponentFilter> = [...componentFilters];
287
+ const index = componentFilters.indexOf(componentFilter);
288
+ if (index >= 0) {
289
+ if (type === ComponentFilterElementType) {
290
+ // $FlowFixMe TODO (filters)
291
+ cloned[index] = ({
292
+ type,
293
+ isEnabled: componentFilter.isEnabled,
294
+ value: ElementTypeHostComponent,
295
+ }: ElementTypeComponentFilter);
296
+ } else if (
297
+ type === ComponentFilterDisplayName ||
298
+ type === ComponentFilterPath
299
+ ) {
300
+ // $FlowFixMe TODO (filters)
301
+ cloned[index] = ({
302
+ type,
303
+ isEnabled: componentFilter.isEnabled,
304
+ isValid: true,
305
+ value: '',
306
+ }: RegExpComponentFilter);
307
+ }
308
+ }
309
+ return cloned;
310
+ });
311
+ },
312
+ []
313
+ );
314
+
315
+ const updateFilterValueElementType = useCallback(
316
+ (componentFilter: ComponentFilter, value: ElementType) => {
317
+ if (componentFilter.type !== ComponentFilterElementType) {
318
+ throw Error('Invalid value for element type filter');
319
+ }
320
+
321
+ setComponentFilters(componentFilters => {
322
+ const cloned: Array<ComponentFilter> = [...componentFilters];
323
+ const index = componentFilters.indexOf(componentFilter);
324
+ if (index >= 0) {
325
+ // $FlowFixMe TODO (filters)
326
+ cloned[index] = {
327
+ ...componentFilter,
328
+ value,
329
+ };
330
+ }
331
+ return cloned;
332
+ });
333
+ },
334
+ []
335
+ );
336
+
337
+ const updateFilterValueRegExp = useCallback(
338
+ (componentFilter: ComponentFilter, value: string) => {
339
+ if (componentFilter.type === ComponentFilterElementType) {
340
+ throw Error('Invalid value for element type filter');
341
+ }
342
+
343
+ setComponentFilters(componentFilters => {
344
+ const cloned: Array<ComponentFilter> = [...componentFilters];
345
+ const index = componentFilters.indexOf(componentFilter);
346
+ if (index >= 0) {
347
+ let isValid = true;
348
+ try {
349
+ new RegExp(value);
350
+ } catch (error) {
351
+ isValid = false;
352
+ }
353
+ // $FlowFixMe TODO (filters)
354
+ cloned[index] = {
355
+ ...componentFilter,
356
+ isValid,
357
+ value,
358
+ };
359
+ }
360
+ return cloned;
361
+ });
362
+ },
363
+ []
364
+ );
365
+
366
+ const removeFilter = useCallback((index: number) => {
367
+ setComponentFilters(componentFilters => {
368
+ const cloned: Array<ComponentFilter> = [...componentFilters];
369
+ cloned.splice(index, 1);
370
+ return cloned;
371
+ });
372
+ }, []);
373
+
374
+ const saveFilters = useCallback(() => {
375
+ store.componentFilters = [...componentFilters];
376
+ }, [componentFilters, store]);
377
+
378
+ const toggleFilterIsEnabled = useCallback(
379
+ (componentFilter: ComponentFilter, isEnabled: boolean) => {
380
+ setComponentFilters(componentFilters => {
381
+ const cloned: Array<ComponentFilter> = [...componentFilters];
382
+ const index = componentFilters.indexOf(componentFilter);
383
+ if (index >= 0) {
384
+ // $FlowFixMe TODO (filters)
385
+ cloned[index] = {
386
+ ...cloned[index],
387
+ isEnabled,
388
+ };
389
+ }
390
+ return cloned;
391
+ });
392
+ },
393
+ []
394
+ );
395
+
396
+ return {
397
+ addFilter,
398
+ changeFilterType,
399
+ componentFilters,
400
+ removeFilter,
401
+ saveFilters,
402
+ toggleFilterIsEnabled,
403
+ updateFilterValueElementType,
404
+ updateFilterValueRegExp,
405
+ };
406
+}
src/devtools/views/Components/ComponentFiltersModalContext.js
new
+37
@@ -0,0 +1,37 @@
1
+// @flow
2
+
3
+import React, { createContext, useMemo, useState } from 'react';
4
+
5
+type Context = {|
6
+ isModalShowing: boolean,
7
+ setIsModalShowing: (value: boolean) => void,
8
+|};
9
+
10
+const ComponentFiltersModalContext = createContext<Context>(
11
+ ((null: any): Context)
12
+);
13
+ComponentFiltersModalContext.displayName = 'ComponentFiltersModalContext';
14
+
15
+type Props = {|
16
+ children: React$Node,
17
+|};
18
+
19
+function ComponentFiltersModalContextController({ children }: Props) {
20
+ const [isModalShowing, setIsModalShowing] = useState(false);
21
+
22
+ const value = useMemo(
23
+ () => ({
24
+ isModalShowing,
25
+ setIsModalShowing,
26
+ }),
27
+ [isModalShowing]
28
+ );
29
+
30
+ return (
31
+ <ComponentFiltersModalContext.Provider value={value}>
32
+ {children}
33
+ </ComponentFiltersModalContext.Provider>
34
+ );
35
+}
36
+
37
+export { ComponentFiltersModalContext, ComponentFiltersModalContextController };
src/devtools/views/Components/ToggleComponentFiltersModalButton.js
new
+42
@@ -0,0 +1,42 @@
1
+// @flow
2
+
3
+import React, { useContext, useMemo } from 'react';
4
+import { useSubscription } from '../hooks';
5
+import { ComponentFiltersModalContext } from './ComponentFiltersModalContext';
6
+import { StoreContext } from '../context';
7
+import Toggle from '../Toggle';
8
+import ButtonIcon from '../ButtonIcon';
9
+import Store from 'src/devtools/store';
10
+
11
+export default function ToggleCommitFilterModalButton() {
12
+ const store = useContext(StoreContext);
13
+
14
+ const { isModalShowing, setIsModalShowing } = useContext(
15
+ ComponentFiltersModalContext
16
+ );
17
+
18
+ // Re-mounting a tree while profiling is in progress might break a lot of assumptions.
19
+ // If necessary, we could support this- but it doesn't seem like a necessary use case.
20
+ const isProfilingSubscription = useMemo(
21
+ () => ({
22
+ getCurrentValue: () => store.isProfiling,
23
+ subscribe: (callback: Function) => {
24
+ store.addListener('isProfiling', callback);
25
+ return () => store.removeListener('isProfiling', callback);
26
+ },
27
+ }),
28
+ [store]
29
+ );
30
+ const isProfiling = useSubscription<boolean, Store>(isProfilingSubscription);
31
+
32
+ return (
33
+ <Toggle
34
+ isChecked={isModalShowing}
35
+ isDisabled={isProfiling}
36
+ onChange={setIsModalShowing}
37
+ title="Filter preferences"
38
+ >
39
+ <ButtonIcon type="filter" />
40
+ </Toggle>
41
+ );
42
+}
src/devtools/views/Components/Tree.css
+1
@@ -1,4 +1,5 @@
1
.Tree {
2
+ position: relative;
3
height: 100%;
4
width: 100%;
5
display: flex;
src/devtools/views/Components/Tree.js
+43
-35
@@ -18,6 +18,9 @@ import ElementView from './Element';
18
import InspectHostNodesToggle from './InspectHostNodesToggle';
19
import OwnersStack from './OwnersStack';
20
import SearchInput from './SearchInput';
21
+import { ComponentFiltersModalContextController } from './ComponentFiltersModalContext';
22
+import ToggleComponentFiltersModalButton from './ToggleComponentFiltersModalButton';
23
+import ComponentFiltersModal from './ComponentFiltersModal';
24
25
import styles from './Tree.css';
26
@@ -269,42 +272,47 @@ export default function Tree(props: Props) {
272
);
273
274
return (
272
- <div className={styles.Tree} ref={treeRef}>
273
- <div className={styles.SearchInput}>
274
- <InspectHostNodesToggle />
275
- <div className={styles.VRule} />
276
- {ownerStack.length > 0 ? <OwnersStack /> : <SearchInput />}
275
+ <ComponentFiltersModalContextController>
276
+ <div className={styles.Tree} ref={treeRef}>
277
+ <div className={styles.SearchInput}>
278
+ <InspectHostNodesToggle />
279
+ <div className={styles.VRule} />
280
+ {ownerStack.length > 0 ? <OwnersStack /> : <SearchInput />}
281
+ <div className={styles.VRule} />
282
+ <ToggleComponentFiltersModalButton />
283
+ </div>
284
+ <div
285
+ className={styles.AutoSizerWrapper}
286
+ onBlur={handleBlur}
287
+ onFocus={handleFocus}
288
+ onKeyPress={handleKeyPress}
289
+ onMouseMove={handleMouseMove}
290
+ onMouseLeave={handleMouseLeave}
291
+ ref={focusTargetRef}
292
+ tabIndex={0}
293
+ >
294
+ <AutoSizer>
295
+ {({ height, width }) => (
296
+ // $FlowFixMe https://github.com/facebook/flow/issues/7341
297
+ <FixedSizeList
298
+ className={styles.List}
299
+ height={height}
300
+ innerElementType={InnerElementType}
301
+ itemCount={numElements}
302
+ itemData={itemData}
303
+ itemSize={lineHeight}
304
+ overscanCount={3}
305
+ ref={listRef}
306
+ width={width}
307
+ >
308
+ {ElementView}
309
+ </FixedSizeList>
310
+ )}
311
+ </AutoSizer>
312
+ </div>
313
+ <ComponentFiltersModal />
314
</div>
278
- <div
279
- className={styles.AutoSizerWrapper}
280
- onBlur={handleBlur}
281
- onFocus={handleFocus}
282
- onKeyPress={handleKeyPress}
283
- onMouseMove={handleMouseMove}
284
- onMouseLeave={handleMouseLeave}
285
- ref={focusTargetRef}
286
- tabIndex={0}
287
- >
288
- <AutoSizer>
289
- {({ height, width }) => (
290
- // $FlowFixMe https://github.com/facebook/flow/issues/7341
291
- <FixedSizeList
292
- className={styles.List}
293
- height={height}
294
- innerElementType={InnerElementType}
295
- itemCount={numElements}
296
- itemData={itemData}
297
- itemSize={lineHeight}
298
- overscanCount={3}
299
- ref={listRef}
300
- width={width}
301
- >
302
- {ElementView}
303
- </FixedSizeList>
304
- )}
305
- </AutoSizer>
306
- </div>
307
- </div>
315
+ </ComponentFiltersModalContextController>
316
);
317
}
318
src/devtools/views/Profiler/ClearProfilingDataButton.js
+1
-1
@@ -18,7 +18,7 @@ export default function ClearProfilingDataButton() {
18
onClick={clear}
19
title="Clear profiling data"
20
>
21
- <ButtonIcon type="cancel" />
21
+ <ButtonIcon type="clear" />
22
</Button>
23
);
24
}
src/devtools/views/Settings/Settings.js
-58
@@ -1,7 +1,6 @@
1
// @flow
2
3
import React, { useCallback, useContext, useMemo } from 'react';
4
-import { ElementTypeHostComponent } from 'src/types';
4
import { useSubscription } from '../hooks';
5
import { StoreContext } from '../context';
6
import { SettingsContext } from './SettingsContext';
@@ -10,8 +9,6 @@ import portaledContent from '../portaledContent';
9
10
import styles from './Settings.css';
11
13
-import type { FilterPreferences } from 'src/types';
14
-
12
function Settings(_: {||}) {
13
const store = useContext(StoreContext);
14
const { displayDensity, setDisplayDensity, theme, setTheme } = useContext(
@@ -46,49 +43,6 @@ function Settings(_: {||}) {
43
collapseNodesByDefaultSubscription
44
);
45
49
- // Re-mounting a tree while profiling is in progress might break a lot of assumptions.
50
- // If necessary, we could support this- but it doesn't seem like a necessary use case.
51
- const isProfilingSubscription = useMemo(
52
- () => ({
53
- getCurrentValue: () => store.isProfiling,
54
- subscribe: (callback: Function) => {
55
- store.addListener('isProfiling', callback);
56
- return () => store.removeListener('isProfiling', callback);
57
- },
58
- }),
59
- [store]
60
- );
61
- const isProfiling = useSubscription<boolean, Store>(isProfilingSubscription);
62
-
63
- const filterPreferencesSubscription = useMemo(
64
- () => ({
65
- getCurrentValue: () => store.filterPreferences,
66
- subscribe: (callback: Function) => {
67
- store.addListener('filterPreferences', callback);
68
- return () => store.removeListener('filterPreferences', callback);
69
- },
70
- }),
71
- [store]
72
- );
73
- const filterPreferences = useSubscription<FilterPreferences, Store>(
74
- filterPreferencesSubscription
75
- );
76
-
77
- const updateFilterPreferences = useCallback(
78
- ({ currentTarget }) => {
79
- const filterPreferences = store.filterPreferences;
80
- if (currentTarget.checked) {
81
- filterPreferences.hideElementsWithTypes.add(ElementTypeHostComponent);
82
- } else {
83
- filterPreferences.hideElementsWithTypes.delete(
84
- ElementTypeHostComponent
85
- );
86
- }
87
- store.filterPreferences = { ...filterPreferences };
88
- },
89
- [store]
90
- );
91
-
46
const updateDisplayDensity = useCallback(
47
({ currentTarget }) => {
48
setDisplayDensity(currentTarget.value);
@@ -189,18 +143,6 @@ function Settings(_: {||}) {
143
/>{' '}
144
Collapse newly added components by default
145
</label>
192
-
193
- <label className={styles.CheckboxOption}>
194
- <input
195
- type="checkbox"
196
- checked={filterPreferences.hideElementsWithTypes.has(
197
- ElementTypeHostComponent
198
- )}
199
- disabled={isProfiling}
200
- onChange={updateFilterPreferences}
201
- />{' '}
202
- Hide host components (e.g. <code><div></code>)
203
- </label>
146
</div>
147
148
{store.supportsCaptureScreenshots && (
src/devtools/views/Settings/SettingsContext.js
+5
@@ -247,6 +247,11 @@ function updateThemeVariables(
247
updateStyleHelper(theme, 'color-tab-selected-border', documentElements);
248
updateStyleHelper(theme, 'color-text', documentElements);
249
updateStyleHelper(theme, 'color-text-selected', documentElements);
250
+ updateStyleHelper(theme, 'color-toggle-background-invalid', documentElements);
251
+ updateStyleHelper(theme, 'color-toggle-background-on', documentElements);
252
+ updateStyleHelper(theme, 'color-toggle-background-off', documentElements);
253
+ updateStyleHelper(theme, 'color-toggle-text', documentElements);
254
+ updateStyleHelper(theme, 'color-toggle-text', documentElements);
255
updateStyleHelper(theme, 'color-tooltip-background', documentElements);
256
updateStyleHelper(theme, 'color-tooltip-text', documentElements);
257
src/devtools/views/root.css
+8
@@ -48,6 +48,10 @@
48
--light-color-tab-selected-border: #0088fa;
49
--light-color-text: #000000;
50
--light-color-text-selected: #ffffff;
51
+ --light-color-toggle-background-invalid: #fc3a4b;
52
+ --light-color-toggle-background-on: #0088fa;
53
+ --light-color-toggle-background-off: #cfd1d5;
54
+ --light-color-toggle-text: #ffffff;
55
--light-color-tooltip-background: rgba(0, 0, 0, 0.9);
56
--light-color-tooltip-text: #ffffff;
57
@@ -96,6 +100,10 @@
100
--dark-color-tab-selected-border: #178fb9;
101
--dark-color-text: #ffffff;
102
--dark-color-text-selected: #ffffff;
103
+ --dark-color-toggle-background-invalid: #fc3a4b;
104
+ --dark-color-toggle-background-on: #178fb9;
105
+ --dark-color-toggle-background-off: #777d88;
106
+ --dark-color-toggle-text: #ffffff;
107
--dark-color-tooltip-background: rgba(255, 255, 255, 0.9);
108
--dark-color-tooltip-text: #000000;
109
src/types.js
+28
-10
@@ -13,7 +13,7 @@ export type Wall = {|
13
|};
14
15
// WARNING
16
-// The values below are referenced by FilterPreferences (which is saved via localStorage).
16
+// The values below are referenced by ComponentFilters (which are 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;
@@ -34,15 +34,33 @@ 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
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>,
37
+// WARNING
38
+// The values below are referenced by ComponentFilters (which are saved via localStorage).
39
+// Do not change them or it will break previously saved user customizations.
40
+// If new filter types are added, use new numbers rather than re-ordering existing ones.
41
+export const ComponentFilterElementType = 1;
42
+export const ComponentFilterDisplayName = 2;
43
+export const ComponentFilterPath = 3;
44
42
- // Hide all elements with displayNames matching one or more of the RegExps in this Set.
43
- hideElementsWithDisplayNames: Set<RegExp>,
45
+export type FilterType = 1 | 2 | 3;
46
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>,
47
+// Hide all elements of types in this Set.
48
+// We hide host components only by default.
49
+export type ElementTypeComponentFilter = {|
50
+ isEnabled: boolean,
51
+ type: 1,
52
+ value: ElementType,
53
|};
54
+
55
+// Hide all elements with displayNames or paths matching one or more of the RegExps in this Set.
56
+// Path filters are only used when elements include debug source location.
57
+export type RegExpComponentFilter = {|
58
+ isEnabled: boolean,
59
+ isValid: boolean,
60
+ type: 2 | 3,
61
+ value: string,
62
+|};
63
+
64
+export type ComponentFilter =
65
+ | ElementTypeComponentFilter
66
+ | RegExpComponentFilter;
src/utils.js
+24
-55
@@ -2,9 +2,9 @@
2
3
import LRU from 'lru-cache';
4
import { LOCAL_STORAGE_FILTER_PREFERENCES_KEY } from './constants';
5
-import { ElementTypeHostComponent } from './types';
5
+import { ComponentFilterElementType, ElementTypeHostComponent } from './types';
6
7
-import type { FilterPreferences } from './types';
7
+import type { ComponentFilter } from './types';
8
9
const FB_MODULE_RE = /^(.*) \[from (.*)\]$/;
10
const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
@@ -81,65 +81,34 @@ function toCodePoint(string: string) {
81
return string.codePointAt(0);
82
}
83
84
-export function getDefaultFilterPreferences(): FilterPreferences {
85
- return {
86
- hideElementsWithTypes: new Set([ElementTypeHostComponent]),
87
- hideElementsWithDisplayNames: new Set(),
88
- hideElementsWithPaths: new Set(),
89
- };
84
+// TODO (filters) Save the filters as the frontend needs them (an array, with type and "enabled" status)
85
+// Convert the fitlers to Sets for the renderer to consume.
86
+
87
+export function getDefaultComponentFilters(): Array<ComponentFilter> {
88
+ return [
89
+ {
90
+ type: ComponentFilterElementType,
91
+ value: ElementTypeHostComponent,
92
+ isEnabled: true,
93
+ },
94
+ ];
95
}
96
92
-function getSavedFilterPreferencesFilter(key, value) {
93
- if (typeof value === 'string' && value.indexOf('__REGEXP__') === 0) {
94
- const match = value.substr(9).match(/\/(.*)\/(.*)?/);
95
- return new RegExp(match[1], match[2] || '');
96
- }
97
- return value;
98
-}
99
-
100
-export function getSavedFilterPreferences(): FilterPreferences {
101
- const raw = localStorage.getItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY);
102
- if (raw != null) {
103
- const json = JSON.parse(raw, getSavedFilterPreferencesFilter);
104
- return {
105
- hideElementsWithTypes: new Set(json.hideElementsWithTypes),
106
- hideElementsWithDisplayNames: new Set(
107
- json.hideElementsWithDisplayNames.map(source => new RegExp(source))
108
- ),
109
- hideElementsWithPaths: new Set(
110
- json.hideElementsWithPaths.map(source => new RegExp(source))
111
- ),
112
- };
113
- } else {
114
- return getDefaultFilterPreferences();
115
- }
116
-}
117
-
118
-function saveFilterPreferencesFilter(key, value) {
119
- if (value instanceof RegExp) {
120
- return '__REGEXP__' + value.toString();
121
- }
122
- return value;
97
+export function getSavedComponentFilters(): Array<ComponentFilter> {
98
+ try {
99
+ const raw = localStorage.getItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY);
100
+ if (raw != null) {
101
+ return JSON.parse(raw);
102
+ }
103
+ } catch (error) {}
104
+ return getDefaultComponentFilters();
105
}
106
125
-export function saveFilterPreferences(
126
- filterPreferences: FilterPreferences
107
+export function saveComponentFilters(
108
+ componentFilters: Array<ComponentFilter>
109
): void {
110
localStorage.setItem(
111
LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
130
- JSON.stringify(
131
- {
132
- hideElementsWithTypes: Array.from(
133
- filterPreferences.hideElementsWithTypes
134
- ),
135
- hideElementsWithDisplayNames: Array.from(
136
- filterPreferences.hideElementsWithDisplayNames
137
- ),
138
- hideElementsWithPaths: Array.from(
139
- filterPreferences.hideElementsWithPaths
140
- ),
141
- },
142
- saveFilterPreferencesFilter
143
- )
112
+ JSON.stringify(componentFilters)
113
);
114
}