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:
committed by
Facebook Github Bot
parent
649688db2a
commit
a8a1869bc8
@@ -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> {
|
||||
|
||||
162
src/plugins/layout/layout2/Search.js
Normal file
162
src/plugins/layout/layout2/Search.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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}>
|
||||
|
||||
Reference in New Issue
Block a user