adding Searchbar

Summary: Adding the searchbar to layout inspector. Most of it's functionality is taken from the existing implementation.

Reviewed By: jknoxville

Differential Revision: D14100533

fbshipit-source-id: 6c3a49658d53c676489886b2599bef425f8f20d3
This commit is contained in:
Daniel Büchele
2019-02-18 04:53:55 -08:00
committed by Facebook Github Bot
parent 649688db2a
commit a8a1869bc8
3 changed files with 183 additions and 2 deletions

View File

@@ -5,7 +5,12 @@
* @format
*/
import type {ElementID, Element, PluginClient} from 'flipper';
import type {
ElementID,
Element,
PluginClient,
ElementSearchResultSet,
} from 'flipper';
import {ElementsInspector} from 'flipper';
import {Component} from 'react';
import debounce from 'lodash.debounce';
@@ -29,6 +34,7 @@ type Props = {
onDataValueChanged: (path: Array<string>, value: any) => void,
setPersistedState: (state: $Shape<PersistedState>) => void,
persistedState: PersistedState,
searchResults: ?ElementSearchResultSet,
};
export default class Inspector extends Component<Props> {

View File

@@ -0,0 +1,162 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import type {PluginClient, ElementSearchResultSet} from 'flipper';
import type {PersistedState} from './';
import {
SearchInput,
SearchBox,
SearchIcon,
LoadingIndicator,
styled,
colors,
} from 'flipper';
import {Component} from 'react';
type SearchResultTree = {|
id: string,
isMatch: Boolean,
hasChildren: boolean,
children: ?Array<SearchResultTree>,
element: Element,
axElement: Element,
|};
type Props = {
client: PluginClient,
inAXMode: boolean,
onSearchResults: (searchResults: ElementSearchResultSet) => void,
setPersistedState: (state: $Shape<PersistedState>) => void,
persistedState: PersistedState,
};
type State = {
value: string,
outstandingSearchQuery: ?string,
};
const LoadingSpinner = styled(LoadingIndicator)({
marginRight: 4,
marginLeft: 3,
marginTop: -1,
});
export default class Search extends Component<Props, State> {
state = {
value: '',
outstandingSearchQuery: null,
};
timer: TimeoutID;
onChange = (e: SyntheticInputEvent<>) => {
clearTimeout(this.timer);
this.setState({
value: e.target.value,
});
this.timer = setTimeout(() => this.performSearch(e.target.value), 200);
};
onKeyDown = (e: SyntheticKeyboardEvent<>) => {
if (e.key === 'Enter') {
this.performSearch(this.state.value);
}
};
performSearch(query: string) {
this.setState({
outstandingSearchQuery: query,
});
if (!query) {
this.displaySearchResults({query: '', results: null});
} else {
this.props.client
.call('getSearchResults', {query, axEnabled: this.props.inAXMode})
.then(response => this.displaySearchResults(response));
}
}
displaySearchResults({
results,
query,
}: {
results: ?SearchResultTree,
query: string,
}) {
this.setState({
outstandingSearchQuery:
query === this.state.outstandingSearchQuery
? null
: this.state.outstandingSearchQuery,
});
const elements = this.getElementsFromSearchResultTree(results);
const expandedElements = elements.reduce(
(acc, {element}) => ({
...acc,
[element.id]: {...element, expanded: true},
}),
{},
);
this.props.setPersistedState({
elements: {
...this.props.persistedState.elements,
...expandedElements,
},
});
this.props.onSearchResults({
matches: new Set(elements.filter(x => x.isMatch).map(x => x.element.id)),
query: query,
});
}
getElementsFromSearchResultTree(
tree: ?SearchResultTree,
): Array<SearchResultTree> {
if (!tree) {
return [];
}
let elements = [
{
id: tree.id,
isMatch: tree.isMatch,
hasChildren: Boolean(tree.children),
element: tree.element,
axElement: tree.axElement,
},
];
if (tree.children) {
for (const child of tree.children) {
elements = elements.concat(this.getElementsFromSearchResultTree(child));
}
}
return elements;
}
render() {
return (
<SearchBox tabIndex={-1}>
<SearchIcon
name="magnifying-glass"
color={colors.macOSTitleBarIcon}
size={16}
/>
<SearchInput
placeholder={'Search'}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
value={this.state.value}
/>
{this.state.outstandingSearchQuery && <LoadingSpinner size={16} />}
</SearchBox>
);
}
}

View File

@@ -5,7 +5,7 @@
* @format
*/
import type {ElementID, Element} from 'flipper';
import type {ElementID, Element, ElementSearchResultSet} from 'flipper';
import {
FlexColumn,
@@ -20,6 +20,7 @@ import {
import Inspector from './Inspector';
import ToolbarIcon from './ToolbarIcon';
import InspectorSidebar from './InspectorSidebar';
import Search from './Search';
type State = {|
init: boolean,
@@ -28,6 +29,7 @@ type State = {|
inAlignmentMode: boolean,
selectedElement: ?ElementID,
selectedAXElement: ?ElementID,
searchResults: ?ElementSearchResultSet,
|};
export type PersistedState = {|
@@ -52,6 +54,7 @@ export default class Layout extends FlipperPlugin<State, void, PersistedState> {
inAlignmentMode: false,
selectedElement: null,
selectedAXElement: null,
searchResults: null,
};
componentDidMount() {
@@ -116,6 +119,7 @@ export default class Layout extends FlipperPlugin<State, void, PersistedState> {
setPersistedState: this.props.setPersistedState,
persistedState: this.props.persistedState,
onDataValueChanged: this.onDataValueChanged,
searchResults: this.state.searchResults,
};
let element;
@@ -152,6 +156,15 @@ export default class Layout extends FlipperPlugin<State, void, PersistedState> {
icon="borders"
active={this.state.inAlignmentMode}
/>
<Search
client={this.client}
setPersistedState={this.props.setPersistedState}
persistedState={this.props.persistedState}
onSearchResults={searchResults =>
this.setState({searchResults})
}
inAXMode={this.state.inAXMode}
/>
</Toolbar>
<FlexRow grow={true}>