convert Layout plugin

Summary: _typescript_

Reviewed By: passy

Differential Revision: D17153997

fbshipit-source-id: 308a070b86430a9256beb93b4d3e5f8d5b6c6e52
This commit is contained in:
Daniel Büchele
2019-09-05 02:46:32 -07:00
committed by Facebook Github Bot
parent 705ba8eaa8
commit ef2c6787fa
11 changed files with 238 additions and 184 deletions

View File

@@ -4,4 +4,15 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
* @format * @format
*/ */
export default [];
import {PluginClient, Client, ElementID} from 'flipper';
import {Logger} from 'src/fb-interfaces/Logger';
export default [] as Array<
(
client: PluginClient,
realClient: Client,
selectedNode: ElementID,
logger: Logger,
) => React.ReactNode
>;

View File

@@ -5,36 +5,36 @@
* @format * @format
*/ */
import type { import {
ElementID, ElementID,
Element, Element,
PluginClient, PluginClient,
ElementsInspector,
ElementSearchResultSet, ElementSearchResultSet,
} from 'flipper'; } from 'flipper';
import {ElementsInspector} from 'flipper';
import {Component} from 'react'; import {Component} from 'react';
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
import {PersistedState, ElementMap} from './';
import type {PersistedState, ElementMap} from './'; import React from 'react';
type GetNodesOptions = { type GetNodesOptions = {
force?: boolean, force?: boolean;
ax?: boolean, ax?: boolean;
forAccessibilityEvent?: boolean, forAccessibilityEvent?: boolean;
}; };
type Props = { type Props = {
ax?: boolean, ax?: boolean;
client: PluginClient, client: PluginClient;
showsSidebar: boolean, showsSidebar: boolean;
inAlignmentMode?: boolean, inAlignmentMode?: boolean;
selectedElement: ?ElementID, selectedElement: ElementID | null | undefined;
selectedAXElement: ?ElementID, selectedAXElement: ElementID | null | undefined;
onSelect: (ids: ?ElementID) => void, onSelect: (ids: ElementID | null | undefined) => void;
onDataValueChanged: (path: Array<string>, value: any) => void, onDataValueChanged: (path: Array<string>, value: any) => void;
setPersistedState: (state: $Shape<PersistedState>) => void, setPersistedState: (state: Partial<PersistedState>) => void;
persistedState: PersistedState, persistedState: PersistedState;
searchResults: ?ElementSearchResultSet, searchResults: ElementSearchResultSet | null;
}; };
export default class Inspector extends Component<Props> { export default class Inspector extends Component<Props> {
@@ -76,8 +76,12 @@ export default class Inspector extends Component<Props> {
const elements: Array<Element> = Object.values( const elements: Array<Element> = Object.values(
this.props.persistedState.AXelements, this.props.persistedState.AXelements,
); );
return elements.find(i => i?.data?.Accessibility?.['accessibility-focused']) const focusedElement = elements.find(i =>
?.id; Boolean(
i.data.Accessibility && i.data.Accessibility['accessibility-focused'],
),
);
return focusedElement ? focusedElement.id : null;
}; };
getAXContextMenuExtensions = () => getAXContextMenuExtensions = () =>
@@ -106,7 +110,7 @@ export default class Inspector extends Component<Props> {
({ ({
nodes, nodes,
}: { }: {
nodes: Array<{id: ElementID, children: Array<ElementID>}>, nodes: Array<{id: ElementID; children: Array<ElementID>}>;
}) => { }) => {
const ids = nodes const ids = nodes
.map(n => [n.id, ...(n.children || [])]) .map(n => [n.id, ...(n.children || [])])
@@ -154,7 +158,11 @@ export default class Inspector extends Component<Props> {
selectedElement selectedElement
]; ];
if (newlySelectedElem) { if (newlySelectedElem) {
this.props.onSelect(newlySelectedElem.extraInfo?.linkedNode); this.props.onSelect(
newlySelectedElem.extraInfo
? newlySelectedElem.extraInfo.linkedNode
: null,
);
} }
} else if ( } else if (
!ax && !ax &&
@@ -166,7 +174,11 @@ export default class Inspector extends Component<Props> {
selectedAXElement selectedAXElement
]; ];
if (newlySelectedAXElem) { if (newlySelectedAXElem) {
this.props.onSelect(newlySelectedAXElem.extraInfo?.linkedNode); this.props.onSelect(
newlySelectedAXElem.extraInfo
? newlySelectedAXElem.extraInfo.linkedNode
: null,
);
} }
} }
} }
@@ -179,11 +191,13 @@ export default class Inspector extends Component<Props> {
(acc: ElementMap, element: Element) => { (acc: ElementMap, element: Element) => {
acc[element.id] = { acc[element.id] = {
...element, ...element,
expanded: this.elements()[element.id]?.expanded, expanded: this.elements()[element.id]
? this.elements()[element.id].expanded
: false,
}; };
return acc; return acc;
}, },
new Map(), {},
); );
this.props.setPersistedState({ this.props.setPersistedState({
[this.props.ax ? 'AXelements' : 'elements']: { [this.props.ax ? 'AXelements' : 'elements']: {
@@ -193,17 +207,19 @@ export default class Inspector extends Component<Props> {
}); });
} }
invalidate(ids: Array<ElementID>): Promise<Array<Element>> { async invalidate(ids: Array<ElementID>): Promise<Array<Element>> {
if (ids.length === 0) { if (ids.length === 0) {
return Promise.resolve([]); return Promise.resolve([]);
} }
return this.getNodes(ids, {}).then((elements: Array<Element>) => { const elements = await this.getNodes(ids, {});
const children = elements const children = elements
.filter((element: Element) => this.elements()[element.id]?.expanded) .filter(
.map((element: Element) => element.children) (element: Element) =>
.reduce((acc, val) => acc.concat(val), []); this.elements()[element.id] && this.elements()[element.id].expanded,
return this.invalidate(children); )
}); .map((element: Element) => element.children)
.reduce((acc, val) => acc.concat(val), []);
return this.invalidate(children);
} }
updateElement(id: ElementID, data: Object) { updateElement(id: ElementID, data: Object) {
@@ -225,7 +241,7 @@ export default class Inspector extends Component<Props> {
// element has no children so we're as deep as we can be // element has no children so we're as deep as we can be
return; return;
} }
return this.getChildren(element.id, {}).then((elements: Array<Element>) => { return this.getChildren(element.id, {}).then(() => {
if (element.children.length >= 2) { if (element.children.length >= 2) {
// element has two or more children so we can stop expanding // element has two or more children so we can stop expanding
return; return;
@@ -245,32 +261,32 @@ export default class Inspector extends Component<Props> {
return this.getNodes(this.elements()[id].children, options); return this.getNodes(this.elements()[id].children, options);
} }
getNodes( async getNodes(
ids: Array<ElementID> = [], ids: Array<ElementID> = [],
options: GetNodesOptions, options: GetNodesOptions,
): Promise<Array<Element>> { ): Promise<Array<Element>> {
const {forAccessibilityEvent} = options;
if (ids.length > 0) { if (ids.length > 0) {
return this.props.client const {forAccessibilityEvent} = options;
.call(this.call().GET_NODES, { const {
elements,
}: {elements: Array<Element>} = await this.props.client.call(
this.call().GET_NODES,
{
ids, ids,
forAccessibilityEvent, forAccessibilityEvent,
selected: false, selected: false,
}) },
.then(({elements}) => { );
elements.forEach(e => this.updateElement(e.id, e)); elements.forEach(e => this.updateElement(e.id, e));
return elements; return elements;
});
} else { } else {
return Promise.resolve([]); return [];
} }
} }
getAndExpandPath(path: Array<ElementID>) { async getAndExpandPath(path: Array<ElementID>) {
return Promise.all(path.map(id => this.getChildren(id, {}))).then(() => { await Promise.all(path.map(id => this.getChildren(id, {})));
this.onElementSelected(path[path.length - 1]); this.onElementSelected(path[path.length - 1]);
});
} }
onElementSelected = debounce((selectedKey: ElementID) => { onElementSelected = debounce((selectedKey: ElementID) => {
@@ -278,7 +294,7 @@ export default class Inspector extends Component<Props> {
this.props.onSelect(selectedKey); this.props.onSelect(selectedKey);
}); });
onElementHovered = debounce((key: ?ElementID) => onElementHovered = debounce((key: ElementID | null | undefined) =>
this.props.client.call(this.call().SET_HIGHLIGHTED, { this.props.client.call(this.call().SET_HIGHLIGHTED, {
id: key, id: key,
isAlignmentMode: this.props.inAlignmentMode, isAlignmentMode: this.props.inAlignmentMode,

View File

@@ -5,22 +5,21 @@
* @format * @format
*/ */
import type {Element} from 'flipper';
import type {PluginClient} from 'flipper';
import type Client from '../../Client.tsx';
import type {Logger} from '../../fb-interfaces/Logger.tsx';
import { import {
ManagedDataInspector, ManagedDataInspector,
Panel, Panel,
FlexCenter, FlexCenter,
styled, styled,
colors, colors,
PluginClient,
SidebarExtensions, SidebarExtensions,
Element,
} from 'flipper'; } from 'flipper';
import Client from '../../Client';
import {Logger} from '../../fb-interfaces/Logger';
import {Component} from 'react'; import {Component} from 'react';
import deepEqual from 'deep-equal';
const deepEqual = require('deep-equal'); import React from 'react';
const NoData = styled(FlexCenter)({ const NoData = styled(FlexCenter)({
fontSize: 18, fontSize: 18,
@@ -30,10 +29,10 @@ const NoData = styled(FlexCenter)({
type OnValueChanged = (path: Array<string>, val: any) => void; type OnValueChanged = (path: Array<string>, val: any) => void;
type InspectorSidebarSectionProps = { type InspectorSidebarSectionProps = {
data: any, data: any;
id: string, id: string;
onValueChanged: ?OnValueChanged, onValueChanged: OnValueChanged | null;
tooltips?: Object, tooltips?: Object;
}; };
class InspectorSidebarSection extends Component<InspectorSidebarSectionProps> { class InspectorSidebarSection extends Component<InspectorSidebarSectionProps> {
@@ -51,7 +50,7 @@ class InspectorSidebarSection extends Component<InspectorSidebarSectionProps> {
); );
} }
extractValue = (val: any, depth: number) => { extractValue = (val: any, _depth: number) => {
if (val && val.__type__) { if (val && val.__type__) {
return { return {
mutable: Boolean(val.__mutable__), mutable: Boolean(val.__mutable__),
@@ -84,14 +83,14 @@ class InspectorSidebarSection extends Component<InspectorSidebarSectionProps> {
} }
} }
type Props = {| type Props = {
element: ?Element, element: Element | null;
tooltips?: Object, tooltips?: Object;
onValueChanged: ?OnValueChanged, onValueChanged: OnValueChanged | null;
client: PluginClient, client: PluginClient;
realClient: Client, realClient: Client;
logger: Logger, logger: Logger;
|}; };
export default class Sidebar extends Component<Props> { export default class Sidebar extends Component<Props> {
render() { render() {
@@ -115,12 +114,13 @@ export default class Sidebar extends Component<Props> {
for (const key in element.data) { for (const key in element.data) {
if (key === 'Extra Sections') { if (key === 'Extra Sections') {
for (const extraSection in element.data[key]) { for (const extraSection in element.data[key]) {
let data = element.data[key][extraSection]; const section = element.data[key][extraSection];
let data = {};
// data might be sent as stringified JSON, we want to parse it for a nicer persentation. // data might be sent as stringified JSON, we want to parse it for a nicer persentation.
if (typeof data === 'string') { if (typeof section === 'string') {
try { try {
data = JSON.parse(data); data = JSON.parse(section);
} catch (e) { } catch (e) {
// data was not a valid JSON, type is required to be an object // data was not a valid JSON, type is required to be an object
console.error( console.error(
@@ -128,6 +128,8 @@ export default class Sidebar extends Component<Props> {
); );
data = {}; data = {};
} }
} else {
data = section;
} }
sections.push( sections.push(
<InspectorSidebarSection <InspectorSidebarSection

View File

@@ -5,15 +5,18 @@
* @format * @format
*/ */
import type {Element, ElementID} from 'flipper'; import {Element} from 'flipper';
import type {PersistedState} from './index'; import {PersistedState} from './index';
import type {SearchResultTree} from './Search'; import {SearchResultTree} from './Search';
// $FlowFixMe
import cloneDeep from 'lodash.clonedeep'; import cloneDeep from 'lodash.clonedeep';
const propsForPersistedState = ( const propsForPersistedState = (
AXMode: boolean, AXMode: boolean,
): {ROOT: string, ELEMENTS: string, ELEMENT: string} => { ): {
ROOT: 'rootAXElement' | 'rootElement';
ELEMENTS: 'AXelements' | 'elements';
ELEMENT: 'axElement' | 'element';
} => {
return { return {
ROOT: AXMode ? 'rootAXElement' : 'rootElement', ROOT: AXMode ? 'rootAXElement' : 'rootElement',
ELEMENTS: AXMode ? 'AXelements' : 'elements', ELEMENTS: AXMode ? 'AXelements' : 'elements',
@@ -25,14 +28,14 @@ function constructSearchResultTree(
node: Element, node: Element,
isMatch: boolean, isMatch: boolean,
children: Array<SearchResultTree>, children: Array<SearchResultTree>,
AXMode: boolean, _AXMode: boolean,
AXNode: ?Element, AXNode: Element | null,
): SearchResultTree { ): SearchResultTree {
const searchResult = { const searchResult = {
id: node.id, id: node.id,
isMatch, isMatch,
hasChildren: children.length > 0, hasChildren: children.length > 0,
children: children.length > 0 ? children : null, children: children.length > 0 ? children : [],
element: node, element: node,
axElement: AXNode, axElement: AXNode,
}; };
@@ -49,7 +52,7 @@ export function searchNodes(
query: string, query: string,
AXMode: boolean, AXMode: boolean,
state: PersistedState, state: PersistedState,
): ?SearchResultTree { ): SearchResultTree | null {
// Even if the axMode is true, we will have to search the normal elements too. // Even if the axMode is true, we will have to search the normal elements too.
// The AXEelements will automatically populated in constructSearchResultTree // The AXEelements will automatically populated in constructSearchResultTree
const elements = state[propsForPersistedState(false).ELEMENTS]; const elements = state[propsForPersistedState(false).ELEMENTS];
@@ -83,20 +86,19 @@ class ProxyArchiveClient {
this.persistedState = cloneDeep(persistedState); this.persistedState = cloneDeep(persistedState);
} }
persistedState: PersistedState; persistedState: PersistedState;
subscribe(method: string, callback: (params: any) => void): void { subscribe(_method: string, _callback: (params: any) => void): void {
return; return;
} }
supportsMethod(method: string): Promise<boolean> { supportsMethod(_method: string): Promise<boolean> {
return Promise.resolve(false); return Promise.resolve(false);
} }
send(method: string, params?: Object): void { send(_method: string, _params?: Object): void {
return; return;
} }
call(method: string, params?: Object): Promise<any> { call(method: string, paramaters?: {[key: string]: any}): Promise<any> {
const paramaters = params;
switch (method) { switch (method) {
case 'getRoot': { case 'getRoot': {
const {rootElement} = this.persistedState; const {rootElement} = this.persistedState;
@@ -118,7 +120,7 @@ class ProxyArchiveClient {
} }
const {ids} = paramaters; const {ids} = paramaters;
const arr: Array<Element> = []; const arr: Array<Element> = [];
for (const id: ElementID of ids) { for (const id of ids) {
arr.push(this.persistedState.elements[id]); arr.push(this.persistedState.elements[id]);
} }
return Promise.resolve({elements: arr}); return Promise.resolve({elements: arr});
@@ -129,7 +131,7 @@ class ProxyArchiveClient {
} }
const {ids} = paramaters; const {ids} = paramaters;
const arr: Array<Element> = []; const arr: Array<Element> = [];
for (const id: ElementID of ids) { for (const id of ids) {
arr.push(this.persistedState.AXelements[id]); arr.push(this.persistedState.AXelements[id]);
} }
return Promise.resolve({elements: arr}); return Promise.resolve({elements: arr});
@@ -148,7 +150,7 @@ class ProxyArchiveClient {
new Error('query is not passed as a params to getSearchResults'), new Error('query is not passed as a params to getSearchResults'),
); );
} }
let element = {}; let element: Element;
if (axEnabled) { if (axEnabled) {
if (!rootAXElement) { if (!rootAXElement) {
return Promise.reject(new Error('rootAXElement is undefined')); return Promise.reject(new Error('rootAXElement is undefined'));

View File

@@ -5,10 +5,11 @@
* @format * @format
*/ */
import type {PluginClient, ElementSearchResultSet, Element} from 'flipper'; import {PersistedState, ElementMap} from './';
import type {PersistedState, ElementMap} from './';
import { import {
PluginClient,
ElementSearchResultSet,
Element,
SearchInput, SearchInput,
SearchBox, SearchBox,
SearchIcon, SearchIcon,
@@ -17,28 +18,29 @@ import {
colors, colors,
} from 'flipper'; } from 'flipper';
import {Component} from 'react'; import {Component} from 'react';
import React from 'react';
export type SearchResultTree = {| export type SearchResultTree = {
id: string, id: string;
isMatch: boolean, isMatch: boolean;
hasChildren: boolean, hasChildren: boolean;
children: ?Array<SearchResultTree>, children: Array<SearchResultTree>;
element: Element, element: Element;
axElement: ?Element, // Not supported in iOS axElement: Element | null; // Not supported in iOS
|}; };
type Props = { type Props = {
client: PluginClient, client: PluginClient;
inAXMode: boolean, inAXMode: boolean;
onSearchResults: (searchResults: ElementSearchResultSet) => void, onSearchResults: (searchResults: ElementSearchResultSet) => void;
setPersistedState: (state: $Shape<PersistedState>) => void, setPersistedState: (state: Partial<PersistedState>) => void;
persistedState: PersistedState, persistedState: PersistedState;
initialQuery: ?string, initialQuery: string | null;
}; };
type State = { type State = {
value: string, value: string;
outstandingSearchQuery: ?string, outstandingSearchQuery: string | null;
}; };
const LoadingSpinner = styled(LoadingIndicator)({ const LoadingSpinner = styled(LoadingIndicator)({
@@ -53,16 +55,18 @@ export default class Search extends Component<Props, State> {
outstandingSearchQuery: null, outstandingSearchQuery: null,
}; };
timer: TimeoutID; timer: NodeJS.Timeout | undefined;
onChange = (e: SyntheticInputEvent<>) => { onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
clearTimeout(this.timer); if (this.timer) {
clearTimeout(this.timer);
}
const {value} = e.target; const {value} = e.target;
this.setState({value}); this.setState({value});
this.timer = setTimeout(() => this.performSearch(value), 200); this.timer = setTimeout(() => this.performSearch(value), 200);
}; };
onKeyDown = (e: SyntheticKeyboardEvent<>) => { onKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
this.performSearch(this.state.value); this.performSearch(this.state.value);
} }
@@ -73,7 +77,9 @@ export default class Search extends Component<Props, State> {
const queryString = this.props.initialQuery const queryString = this.props.initialQuery
? this.props.initialQuery ? this.props.initialQuery
: ''; : '';
clearTimeout(this.timer); if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(() => this.performSearch(queryString), 200); this.timer = setTimeout(() => this.performSearch(queryString), 200);
} }
} }
@@ -102,8 +108,8 @@ export default class Search extends Component<Props, State> {
results, results,
query, query,
}: { }: {
results: ?SearchResultTree, results: SearchResultTree | null;
query: string, query: string;
}, },
axMode: boolean, axMode: boolean,
) { ) {
@@ -159,13 +165,14 @@ export default class Search extends Component<Props, State> {
} }
getElementsFromSearchResultTree( getElementsFromSearchResultTree(
tree: ?SearchResultTree, tree: SearchResultTree | null,
): Array<SearchResultTree> { ): Array<SearchResultTree> {
if (!tree) { if (!tree) {
return []; return [];
} }
let elements = [ let elements = [
{ {
children: [] as Array<SearchResultTree>,
id: tree.id, id: tree.id,
isMatch: tree.isMatch, isMatch: tree.isMatch,
hasChildren: Boolean(tree.children), hasChildren: Boolean(tree.children),

View File

@@ -6,13 +6,14 @@
*/ */
import {Glyph, styled, colors} from 'flipper'; import {Glyph, styled, colors} from 'flipper';
import React from 'react';
type Props = {| type Props = {
title: string, title: string;
icon: string, icon: string;
active: boolean, active: boolean;
onClick: () => void, onClick: () => void;
|}; };
const ToolbarIcon = styled('div')({ const ToolbarIcon = styled('div')({
marginRight: 9, marginRight: 9,

View File

@@ -9,9 +9,9 @@ import {
default as ProxyArchiveClient, default as ProxyArchiveClient,
searchNodes, searchNodes,
} from '../ProxyArchiveClient'; } from '../ProxyArchiveClient';
import type {PersistedState} from '../index'; import {PersistedState, ElementMap} from '../index';
import type {ElementID, Element} from 'flipper'; import {ElementID, Element} from 'flipper';
import type {SearchResultTree} from '../Search'; import {SearchResultTree} from '../Search';
function constructElement( function constructElement(
id: string, id: string,
@@ -49,7 +49,7 @@ function constructPersistedState(axMode: boolean): PersistedState {
let state = constructPersistedState(false); let state = constructPersistedState(false);
function populateChildren(state: PersistedState, axMode: boolean) { function populateChildren(state: PersistedState, axMode: boolean) {
const elements = {}; const elements: ElementMap = {};
elements['root'] = constructElement('root', 'root view', [ elements['root'] = constructElement('root', 'root view', [
'child0', 'child0',
'child1', 'child1',
@@ -95,7 +95,7 @@ beforeEach(() => {
}); });
test('test the searchNode for root in axMode false', async () => { test('test the searchNode for root in axMode false', async () => {
const searchResult: ?SearchResultTree = await searchNodes( const searchResult: SearchResultTree | null = await searchNodes(
state.elements['root'], state.elements['root'],
'root', 'root',
false, false,
@@ -106,7 +106,7 @@ test('test the searchNode for root in axMode false', async () => {
id: 'root', id: 'root',
isMatch: true, isMatch: true,
hasChildren: false, hasChildren: false,
children: null, children: [],
element: state.elements['root'], element: state.elements['root'],
axElement: null, axElement: null,
}); });
@@ -115,7 +115,7 @@ test('test the searchNode for root in axMode false', async () => {
test('test the searchNode for root in axMode true', async () => { test('test the searchNode for root in axMode true', async () => {
state = constructPersistedState(true); state = constructPersistedState(true);
populateChildren(state, true); populateChildren(state, true);
const searchResult: ?SearchResultTree = await searchNodes( const searchResult: SearchResultTree | null = await searchNodes(
state.AXelements['root'], state.AXelements['root'],
'RoOT', 'RoOT',
true, true,
@@ -126,14 +126,14 @@ test('test the searchNode for root in axMode true', async () => {
id: 'root', id: 'root',
isMatch: true, isMatch: true,
hasChildren: false, hasChildren: false,
children: null, children: [],
element: state.AXelements['root'], // Even though AXElement exists, normal element will exist too element: state.AXelements['root'], // Even though AXElement exists, normal element will exist too
axElement: state.AXelements['root'], axElement: state.AXelements['root'],
}); });
}); });
test('test the searchNode which matches just one child', async () => { test('test the searchNode which matches just one child', async () => {
const searchResult: ?SearchResultTree = await searchNodes( const searchResult: SearchResultTree | null = await searchNodes(
state.elements['root'], state.elements['root'],
'child0_child0', 'child0_child0',
false, false,
@@ -154,7 +154,7 @@ test('test the searchNode which matches just one child', async () => {
id: 'child0_child0', id: 'child0_child0',
isMatch: true, isMatch: true,
hasChildren: false, hasChildren: false,
children: null, children: [],
element: state.elements['child0_child0'], element: state.elements['child0_child0'],
axElement: null, axElement: null,
}, },
@@ -169,7 +169,7 @@ test('test the searchNode which matches just one child', async () => {
}); });
test('test the searchNode for which matches multiple child', async () => { test('test the searchNode for which matches multiple child', async () => {
const searchResult: ?SearchResultTree = await searchNodes( const searchResult: SearchResultTree | null = await searchNodes(
state.elements['root'], state.elements['root'],
'child0', 'child0',
false, false,
@@ -190,7 +190,7 @@ test('test the searchNode for which matches multiple child', async () => {
id: 'child0_child0', id: 'child0_child0',
isMatch: true, isMatch: true,
hasChildren: false, hasChildren: false,
children: null, children: [],
element: state.elements['child0_child0'], element: state.elements['child0_child0'],
axElement: null, axElement: null,
}, },
@@ -198,7 +198,7 @@ test('test the searchNode for which matches multiple child', async () => {
id: 'child0_child1', id: 'child0_child1',
isMatch: true, isMatch: true,
hasChildren: false, hasChildren: false,
children: null, children: [],
element: state.elements['child0_child1'], element: state.elements['child0_child1'],
axElement: null, axElement: null,
}, },
@@ -215,7 +215,7 @@ test('test the searchNode for which matches multiple child', async () => {
id: 'child1_child0', id: 'child1_child0',
isMatch: true, isMatch: true,
hasChildren: false, hasChildren: false,
children: null, children: [],
element: state.elements['child1_child0'], element: state.elements['child1_child0'],
axElement: null, axElement: null,
}, },
@@ -231,7 +231,7 @@ test('test the searchNode for which matches multiple child', async () => {
}); });
test('test the searchNode, it should not be case sensitive', async () => { test('test the searchNode, it should not be case sensitive', async () => {
const searchResult: ?SearchResultTree = await searchNodes( const searchResult: SearchResultTree | null = await searchNodes(
state.elements['root'], state.elements['root'],
'ChIlD0', 'ChIlD0',
false, false,
@@ -252,7 +252,7 @@ test('test the searchNode, it should not be case sensitive', async () => {
id: 'child0_child0', id: 'child0_child0',
isMatch: true, isMatch: true,
hasChildren: false, hasChildren: false,
children: null, children: [],
element: state.elements['child0_child0'], element: state.elements['child0_child0'],
axElement: null, axElement: null,
}, },
@@ -260,7 +260,7 @@ test('test the searchNode, it should not be case sensitive', async () => {
id: 'child0_child1', id: 'child0_child1',
isMatch: true, isMatch: true,
hasChildren: false, hasChildren: false,
children: null, children: [],
element: state.elements['child0_child1'], element: state.elements['child0_child1'],
axElement: null, axElement: null,
}, },
@@ -277,7 +277,7 @@ test('test the searchNode, it should not be case sensitive', async () => {
id: 'child1_child0', id: 'child1_child0',
isMatch: true, isMatch: true,
hasChildren: false, hasChildren: false,
children: null, children: [],
element: state.elements['child1_child0'], element: state.elements['child1_child0'],
axElement: null, axElement: null,
}, },
@@ -293,7 +293,7 @@ test('test the searchNode, it should not be case sensitive', async () => {
}); });
test('test the searchNode for non existent query', async () => { test('test the searchNode for non existent query', async () => {
const searchResult: ?SearchResultTree = await searchNodes( const searchResult: SearchResultTree | null = await searchNodes(
state.elements['root'], state.elements['root'],
'Unknown query', 'Unknown query',
false, false,

View File

@@ -5,20 +5,16 @@
* @format * @format
*/ */
import type { import {
ElementID, ElementID,
Element, Element,
ElementSearchResultSet, ElementSearchResultSet,
MiddlewareAPI, MiddlewareAPI,
PluginClient, PluginClient,
} from 'flipper';
import {
FlexColumn, FlexColumn,
FlexRow, FlexRow,
FlipperPlugin, FlipperPlugin,
Toolbar, Toolbar,
Sidebar,
DetailSidebar, DetailSidebar,
VerticalRule, VerticalRule,
Button, Button,
@@ -29,37 +25,42 @@ import ToolbarIcon from './ToolbarIcon';
import InspectorSidebar from './InspectorSidebar'; import InspectorSidebar from './InspectorSidebar';
import Search from './Search'; import Search from './Search';
import ProxyArchiveClient from './ProxyArchiveClient'; import ProxyArchiveClient from './ProxyArchiveClient';
import React from 'react';
type State = {| type State = {
init: boolean, init: boolean;
inTargetMode: boolean, inTargetMode: boolean;
inAXMode: boolean, inAXMode: boolean;
inAlignmentMode: boolean, inAlignmentMode: boolean;
selectedElement: ?ElementID, selectedElement: ElementID | null | undefined;
selectedAXElement: ?ElementID, selectedAXElement: ElementID | null | undefined;
searchResults: ?ElementSearchResultSet, searchResults: ElementSearchResultSet | null;
|}; };
export type ElementMap = {[key: ElementID]: Element}; export type ElementMap = {[key: string]: Element};
export type PersistedState = {| export type PersistedState = {
rootElement: ?ElementID, rootElement: ElementID | null;
rootAXElement: ?ElementID, rootAXElement: ElementID | null;
elements: ElementMap, elements: ElementMap;
AXelements: ElementMap, AXelements: ElementMap;
|}; };
export default class Layout extends FlipperPlugin<State, void, PersistedState> { export default class Layout extends FlipperPlugin<State, any, PersistedState> {
static exportPersistedState = ( static exportPersistedState = async (
callClient: (string, ?Object) => Promise<Object>, callClient: (
persistedState: ?PersistedState, method: 'getAllNodes',
store: ?MiddlewareAPI, ) => Promise<{
): Promise<?PersistedState> => { allNodes: PersistedState;
const defaultPromise = Promise.resolve(persistedState); }>,
persistedState: PersistedState | undefined,
store: MiddlewareAPI | undefined,
): Promise<PersistedState | undefined> => {
if (!store) { if (!store) {
return defaultPromise; return persistedState;
} }
return callClient('getAllNodes').then(({allNodes}) => allNodes); const {allNodes} = await callClient('getAllNodes');
return allNodes;
}; };
static defaultPersistedState = { static defaultPersistedState = {
@@ -69,7 +70,7 @@ export default class Layout extends FlipperPlugin<State, void, PersistedState> {
AXelements: {}, AXelements: {},
}; };
state = { state: State = {
init: false, init: false,
inTargetMode: false, inTargetMode: false,
inAXMode: false, inAXMode: false,
@@ -154,13 +155,12 @@ export default class Layout extends FlipperPlugin<State, void, PersistedState> {
searchResults: this.state.searchResults, searchResults: this.state.searchResults,
}; };
let element; let element: Element | null = null;
if (this.state.inAXMode && this.state.selectedAXElement) { const {selectedAXElement, selectedElement, inAXMode} = this.state;
element = this.props.persistedState.AXelements[ if (inAXMode && selectedAXElement) {
this.state.selectedAXElement element = this.props.persistedState.AXelements[selectedAXElement];
]; } else if (selectedElement) {
} else if (this.state.selectedElement) { element = this.props.persistedState.elements[selectedElement];
element = this.props.persistedState.elements[this.state.selectedElement];
} }
const inspector = ( const inspector = (
@@ -247,7 +247,7 @@ export default class Layout extends FlipperPlugin<State, void, PersistedState> {
compact={true} compact={true}
style={{marginTop: 8, marginRight: 12}} style={{marginTop: 8, marginRight: 12}}
onClick={() => { onClick={() => {
this.props.selectPlugin('YogaPerformance', element.id); this.props.selectPlugin('YogaPerformance', element!.id);
}}> }}>
Analyze Yoga Performance Analyze Yoga Performance
</Button> </Button>

View File

@@ -1,7 +1,7 @@
{ {
"name": "Inspector", "name": "Inspector",
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.tsx",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"deep-equal": "^1.0.1", "deep-equal": "^1.0.1",
@@ -13,5 +13,8 @@
"bugs": { "bugs": {
"email": "oncall+flipper@xmail.facebook.com", "email": "oncall+flipper@xmail.facebook.com",
"url": "https://fb.workplace.com/groups/230455004101832/" "url": "https://fb.workplace.com/groups/230455004101832/"
},
"devDependencies": {
"@types/lodash.clonedeep": "^4.5.6"
} }
} }

View File

@@ -2,6 +2,18 @@
# yarn lockfile v1 # yarn lockfile v1
"@types/lodash.clonedeep@^4.5.6":
version "4.5.6"
resolved "https://registry.yarnpkg.com/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz#3b6c40a0affe0799a2ce823b440a6cf33571d32b"
integrity sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA==
dependencies:
"@types/lodash" "*"
"@types/lodash@*":
version "4.14.138"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.138.tgz#34f52640d7358230308344e579c15b378d91989e"
integrity sha512-A4uJgHz4hakwNBdHNPdxOTkYmXNgmUAKLbXZ7PKGslgeV0Mb8P3BlbYfPovExek1qnod4pDfRbxuzcVs3dlFLg==
deep-equal@^1.0.1: deep-equal@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"

View File

@@ -1,7 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "system", "module": "system",
"lib": ["es7", "dom"], "lib": ["es7", "dom", "es2017"],
"target": "es6", "target": "es6",
"removeComments": true, "removeComments": true,
"preserveConstEnums": true, "preserveConstEnums": true,