@samitouri / QOS-React-2 / commits / ee1b38e4db

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>&lt;div&gt;</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 +}