From ee1b38e4dbfdf674ecdb17d3a07a1375ed01979a Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 27 Apr 2019 10:31:40 -0700 Subject: [PATCH] Added filter preference types and plugged into renderer partly. Lots of work to do still. --- src/backend/renderer.js | 60 ++++++---------------- src/constants.js | 3 +- src/devtools/views/ButtonIcon.js | 20 +++++++- src/devtools/views/Settings/FilterList.css | 4 ++ src/devtools/views/Settings/FilterList.js | 40 +++++++++++++++ src/devtools/views/Settings/Settings.css | 1 + src/devtools/views/Settings/Settings.js | 4 ++ src/devtools/views/hooks.js | 10 +++- src/types.js | 27 +++++----- src/utils.js | 46 ++++++++++++++--- 10 files changed, 147 insertions(+), 68 deletions(-) create mode 100644 src/devtools/views/Settings/FilterList.css create mode 100644 src/devtools/views/Settings/FilterList.js diff --git a/src/backend/renderer.js b/src/backend/renderer.js index 718b148d21..f1ee1bc7da 100644 --- a/src/backend/renderer.js +++ b/src/backend/renderer.js @@ -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 = new Map(); - const filterByNames: Set = new Set(); - const filterByPaths: Set = new Set(); - - function updateFilters(filters: Array): 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; } diff --git a/src/constants.js b/src/constants.js index ea4e7642db..f5c7683d69 100644 --- a/src/constants.js +++ b/src/constants.js @@ -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'; diff --git a/src/devtools/views/ButtonIcon.js b/src/devtools/views/ButtonIcon.js index a42e02bdbc..3099068b5b 100644 --- a/src/devtools/views/ButtonIcon.js +++ b/src/devtools/views/ButtonIcon.js @@ -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 ( { + const clonedFilterPreferences = { ...filterPreferences }; + setFilterPreferences(clonedFilterPreferences); + saveFilterPreferences(clonedFilterPreferences); + }, [filterPreferences]); + + const { hideElementsWithTypes } = filterPreferences; + + return ( + + <div>) + + + ); +} diff --git a/src/devtools/views/Settings/Settings.css b/src/devtools/views/Settings/Settings.css index 9e255d1233..a514192750 100644 --- a/src/devtools/views/Settings/Settings.css +++ b/src/devtools/views/Settings/Settings.css @@ -71,6 +71,7 @@ } .ScreenshotThrottling { + display: inline-block; background-color: var(--color-background-hover); padding: 0.25rem 0.5rem; border-radius: 0.25rem; diff --git a/src/devtools/views/Settings/Settings.js b/src/devtools/views/Settings/Settings.js index b578fe5b38..ae23bbbc7d 100644 --- a/src/devtools/views/Settings/Settings.js +++ b/src/devtools/views/Settings/Settings.js @@ -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(_: {||}) {
Components tree
+ + +
{store.supportsCaptureScreenshots && ( diff --git a/src/devtools/views/hooks.js b/src/devtools/views/hooks.js index 6d48915d20..b9e7dfc0b4 100644 --- a/src/devtools/views/hooks.js +++ b/src/devtools/views/hooks.js @@ -38,14 +38,20 @@ export function useIsOverflowing( // Forked from https://usehooks.com/useLocalStorage/ export function useLocalStorage( 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]); diff --git a/src/types.js b/src/types.js index d831fcef37..b1af981fde 100644 --- a/src/types.js +++ b/src/types.js @@ -12,6 +12,10 @@ export type Wall = {| send: (event: string, payload: any, transferable?: Array) => 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, -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, + + // 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, +|}; diff --git a/src/utils.js b/src/utils.js index 9d07194fc7..b20abd07fd 100644 --- a/src/utils.js +++ b/src/utils.js @@ -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 = new WeakMap(); @@ -81,11 +81,43 @@ function toCodePoint(string: string) { return string.codePointAt(0); } -export function getSavedFilters(): Array { - const filters = localStorage.getItem(LOCAL_STORAGE_FILTERS_KEY); - if (filters != null) { - return ((JSON.parse(filters): any): Array); +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 + ), + }) + ); +}