(server) Multiple Selector on Layout Inspector
Summary: This diff adds multiple selector UI on layout plugin, which shows up when there are more than one component at a touch. This UI allows user to do similar thing to element inspector. Expanding functionality on main component will be added in next diffs. Reviewed By: mweststrate Differential Revision: D21214899 fbshipit-source-id: 5c9cae93122cc4f7c326ccd0878d2b9dddebf62b
This commit is contained in:
committed by
Facebook GitHub Bot
parent
9962b1c687
commit
83a2203751
@@ -20,7 +20,7 @@ import styled from '@emotion/styled';
|
|||||||
import {clipboard, MenuItemConstructorOptions} from 'electron';
|
import {clipboard, MenuItemConstructorOptions} from 'electron';
|
||||||
import React, {MouseEvent, KeyboardEvent} from 'react';
|
import React, {MouseEvent, KeyboardEvent} from 'react';
|
||||||
|
|
||||||
const ROW_HEIGHT = 23;
|
export const ROW_HEIGHT = 23;
|
||||||
|
|
||||||
const backgroundColor = (props: {
|
const backgroundColor = (props: {
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
|
|||||||
@@ -13,11 +13,19 @@ import {
|
|||||||
PluginClient,
|
PluginClient,
|
||||||
ElementsInspector,
|
ElementsInspector,
|
||||||
ElementSearchResultSet,
|
ElementSearchResultSet,
|
||||||
|
FlexColumn,
|
||||||
|
styled,
|
||||||
} from 'flipper';
|
} from 'flipper';
|
||||||
import {Component} from 'react';
|
|
||||||
import {debounce} from 'lodash';
|
import {debounce} from 'lodash';
|
||||||
|
import {Component} from 'react';
|
||||||
import {PersistedState, ElementMap} from './';
|
import {PersistedState, ElementMap} from './';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import MultipleSelectorSection from './MultipleSelectionSection';
|
||||||
|
|
||||||
|
const ElementsInspectorContainer = styled(FlexColumn)({
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
});
|
||||||
|
|
||||||
type GetNodesOptions = {
|
type GetNodesOptions = {
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
@@ -25,7 +33,12 @@ type GetNodesOptions = {
|
|||||||
forAccessibilityEvent?: boolean;
|
forAccessibilityEvent?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ElementSelectorNode = {[id: string]: ElementSelectorNode};
|
export type ElementSelectorNode = {[id: string]: ElementSelectorNode};
|
||||||
|
export type ElementSelectorData = {
|
||||||
|
leaves: Array<ElementID>;
|
||||||
|
tree: ElementSelectorNode;
|
||||||
|
elements: ElementMap;
|
||||||
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
ax?: boolean;
|
ax?: boolean;
|
||||||
@@ -41,7 +54,14 @@ type Props = {
|
|||||||
searchResults: ElementSearchResultSet | null;
|
searchResults: ElementSearchResultSet | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Inspector extends Component<Props> {
|
type State = {
|
||||||
|
elementSelector: ElementSelectorData | null;
|
||||||
|
axElementSelector: ElementSelectorData | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Inspector extends Component<Props, State> {
|
||||||
|
state: State = {elementSelector: null, axElementSelector: null};
|
||||||
|
|
||||||
call() {
|
call() {
|
||||||
return {
|
return {
|
||||||
GET_ROOT: this.props.ax ? 'getAXRoot' : 'getRoot',
|
GET_ROOT: this.props.ax ? 'getAXRoot' : 'getRoot',
|
||||||
@@ -133,12 +153,29 @@ export default class Inspector extends Component<Props> {
|
|||||||
|
|
||||||
this.props.client.subscribe(
|
this.props.client.subscribe(
|
||||||
this.call().SELECT,
|
this.call().SELECT,
|
||||||
({path, tree}: {path?: Array<ElementID>; tree?: ElementSelectorNode}) => {
|
async ({
|
||||||
if (tree) {
|
path,
|
||||||
this._getAndExpandPathFromTree(tree);
|
tree,
|
||||||
} else if (path) {
|
}: {
|
||||||
|
path?: Array<ElementID>;
|
||||||
|
tree?: ElementSelectorNode;
|
||||||
|
}) => {
|
||||||
|
if (path) {
|
||||||
this.getAndExpandPath(path);
|
this.getAndExpandPath(path);
|
||||||
}
|
}
|
||||||
|
if (tree) {
|
||||||
|
const leaves = this.getElementLeaves(tree);
|
||||||
|
const elementArray = await this.getNodes(leaves, {});
|
||||||
|
const elements = leaves.reduce(
|
||||||
|
(acc, cur, idx) => ({...acc, [cur]: elementArray[idx]}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
if (this.props.ax) {
|
||||||
|
this.setState({axElementSelector: {tree, leaves, elements}});
|
||||||
|
} else {
|
||||||
|
this.setState({elementSelector: {tree, leaves, elements}});
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -373,19 +410,33 @@ export default class Inspector extends Component<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const selectorData = this.props.ax
|
||||||
|
? this.state.axElementSelector
|
||||||
|
: this.state.elementSelector;
|
||||||
|
|
||||||
return this.root() ? (
|
return this.root() ? (
|
||||||
<ElementsInspector
|
<ElementsInspectorContainer>
|
||||||
onElementSelected={this.onElementSelected}
|
<ElementsInspector
|
||||||
onElementHovered={this.onElementHovered}
|
onElementSelected={this.onElementSelected}
|
||||||
onElementExpanded={this.onElementExpanded}
|
onElementHovered={this.onElementHovered}
|
||||||
onValueChanged={this.props.onDataValueChanged}
|
onElementExpanded={this.onElementExpanded}
|
||||||
searchResults={this.props.searchResults}
|
onValueChanged={this.props.onDataValueChanged}
|
||||||
selected={this.selected()}
|
searchResults={this.props.searchResults}
|
||||||
root={this.root()}
|
selected={this.selected()}
|
||||||
elements={this.elements()}
|
root={this.root()}
|
||||||
focused={this.focused()}
|
elements={this.elements()}
|
||||||
contextMenuExtensions={this.getAXContextMenuExtensions()}
|
focused={this.focused()}
|
||||||
/>
|
contextMenuExtensions={this.getAXContextMenuExtensions()}
|
||||||
|
/>
|
||||||
|
{selectorData && selectorData.leaves.length > 1 ? (
|
||||||
|
<MultipleSelectorSection
|
||||||
|
initialSelectedElement={this.selected()}
|
||||||
|
elements={selectorData.elements}
|
||||||
|
onElementSelected={this.onElementSelected}
|
||||||
|
onElementHovered={this.onElementHovered}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</ElementsInspectorContainer>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
84
desktop/plugins/layout/MultipleSelectionSection.tsx
Normal file
84
desktop/plugins/layout/MultipleSelectionSection.tsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
FlexColumn,
|
||||||
|
FlexBox,
|
||||||
|
Element,
|
||||||
|
ElementID,
|
||||||
|
ElementsInspector,
|
||||||
|
colors,
|
||||||
|
styled,
|
||||||
|
} from 'flipper';
|
||||||
|
import React, {memo, useState} from 'react';
|
||||||
|
import {ROW_HEIGHT} from '../../app/src/ui/components/elements-inspector/elements';
|
||||||
|
|
||||||
|
const MultipleSelectorSectionContainer = styled(FlexColumn)({
|
||||||
|
maxHeight: 3 * ROW_HEIGHT + 24,
|
||||||
|
});
|
||||||
|
|
||||||
|
const MultipleSelectorSectionTitle = styled(FlexBox)({
|
||||||
|
cursor: 'pointer',
|
||||||
|
backgroundColor: '#f6f7f9',
|
||||||
|
padding: '2px',
|
||||||
|
paddingLeft: '9px',
|
||||||
|
width: '325px',
|
||||||
|
height: '20px',
|
||||||
|
fontWeight: 500,
|
||||||
|
boxShadow: '2px 2px 2px #ccc',
|
||||||
|
border: `1px solid ${colors.light20}`,
|
||||||
|
borderTopLeftRadius: '4px',
|
||||||
|
borderTopRightRadius: '4px',
|
||||||
|
textAlign: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
type MultipleSelectorSectionProps = {
|
||||||
|
initialSelectedElement: ElementID | null | undefined;
|
||||||
|
elements: {[id: string]: Element};
|
||||||
|
onElementSelected: (key: string) => void;
|
||||||
|
onElementHovered:
|
||||||
|
| ((key: string | null | undefined) => any)
|
||||||
|
| null
|
||||||
|
| undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MultipleSelectorSection: React.FC<MultipleSelectorSectionProps> = memo(
|
||||||
|
(props: MultipleSelectorSectionProps) => {
|
||||||
|
const {
|
||||||
|
initialSelectedElement,
|
||||||
|
elements,
|
||||||
|
onElementSelected,
|
||||||
|
onElementHovered,
|
||||||
|
} = props;
|
||||||
|
const [selectedId, setSelectedId] = useState<ElementID | null | undefined>(
|
||||||
|
initialSelectedElement,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<MultipleSelectorSectionContainer>
|
||||||
|
<MultipleSelectorSectionTitle>
|
||||||
|
Multiple elements found at the target coordinates
|
||||||
|
</MultipleSelectorSectionTitle>
|
||||||
|
<ElementsInspector
|
||||||
|
onElementSelected={(key: string) => {
|
||||||
|
setSelectedId(key);
|
||||||
|
onElementSelected(key);
|
||||||
|
}}
|
||||||
|
onElementHovered={onElementHovered}
|
||||||
|
onElementExpanded={() => {}}
|
||||||
|
onValueChanged={null}
|
||||||
|
root={null}
|
||||||
|
selected={selectedId}
|
||||||
|
elements={elements}
|
||||||
|
/>
|
||||||
|
</MultipleSelectorSectionContainer>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default MultipleSelectorSection;
|
||||||
Reference in New Issue
Block a user