Cleanup js in layout inspector, add check for ios

Summary: Clean up javascript. Add a check to remove ax mode toggle if using ios. Add safety check for extraInfo where it hasn't been added yet.

Reviewed By: danielbuechele

Differential Revision: D9070574

fbshipit-source-id: 49ac781c01ea47239d6c24089976497371973726
This commit is contained in:
Sara Valderrama
2018-08-01 13:30:37 -07:00
committed by Facebook Github Bot
parent 06e70a1555
commit 30a19901ee
2 changed files with 153 additions and 133 deletions

View File

@@ -35,23 +35,25 @@ import debounce from 'lodash.debounce';
export type InspectorState = {| export type InspectorState = {|
initialised: boolean, initialised: boolean,
AXinitialised: boolean,
selected: ?ElementID, selected: ?ElementID,
AXselected: ?ElementID,
AXfocused: ?ElementID,
root: ?ElementID, root: ?ElementID,
AXroot: ?ElementID,
elements: {[key: ElementID]: Element}, elements: {[key: ElementID]: Element},
AXelements: {[key: ElementID]: Element},
isSearchActive: boolean, isSearchActive: boolean,
inAXMode: boolean,
searchResults: ?ElementSearchResultSet, searchResults: ?ElementSearchResultSet,
outstandingSearchQuery: ?string, outstandingSearchQuery: ?string,
// properties for ax mode
AXinitialised: boolean,
AXselected: ?ElementID,
AXfocused: ?ElementID,
AXroot: ?ElementID,
AXelements: {[key: ElementID]: Element},
inAXMode: boolean,
AXtoNonAXMapping: {[key: ElementID]: ElementID}, AXtoNonAXMapping: {[key: ElementID]: ElementID},
|}; |};
type SelectElementArgs = {| type SelectElementArgs = {|
key: ElementID, key: ElementID,
AXkey: ElementID,
|}; |};
type ExpandElementArgs = {| type ExpandElementArgs = {|
@@ -84,6 +86,11 @@ type GetNodesResult = {|
elements: Array<Element>, elements: Array<Element>,
|}; |};
type GetNodesOptions = {|
force: boolean,
ax: boolean,
|};
type SearchResultTree = {| type SearchResultTree = {|
id: string, id: string,
isMatch: Boolean, isMatch: Boolean,
@@ -160,53 +167,28 @@ export default class Layout extends SonarPlugin<InspectorState> {
state = { state = {
elements: {}, elements: {},
AXelements: {},
initialised: false, initialised: false,
AXinitialised: false,
isSearchActive: false, isSearchActive: false,
inAXMode: false,
root: null, root: null,
AXroot: null,
selected: null, selected: null,
AXselected: null,
AXfocused: null,
searchResults: null, searchResults: null,
outstandingSearchQuery: null, outstandingSearchQuery: null,
// properties for ax mode
inAXMode: false,
AXelements: {},
AXinitialised: false,
AXroot: null,
AXselected: null,
AXfocused: null,
AXtoNonAXMapping: {}, AXtoNonAXMapping: {},
}; };
reducers = { reducers = {
SelectElement(state: InspectorState, {key}: SelectElementArgs) { SelectElement(state: InspectorState, {key, AXkey}: SelectElementArgs) {
const linkedAXNode =
state.elements[key] &&
state.elements[key].extraInfo &&
state.elements[key].extraInfo.linkedAXNode;
// element only in main tree with linkedAXNode selected
if (linkedAXNode) {
return { return {
selected: key, selected: key,
AXselected: linkedAXNode, AXselected: AXkey,
}; };
// element only in AX tree with linked nonAX element selected
} else if (
(!state.elements[key] ||
state.elements[key].name === 'ComponentHost') &&
state.AXtoNonAXMapping[key]
) {
return {
selected: state.AXtoNonAXMapping[key],
AXselected: key,
};
// keys are same for both trees or 'linked' element does not exist
} else {
return {
selected: key,
AXselected: key,
};
}
}, },
ExpandElement(state: InspectorState, {expand, key}: ExpandElementArgs) { ExpandElement(state: InspectorState, {expand, key}: ExpandElementArgs) {
@@ -218,13 +200,6 @@ export default class Layout extends SonarPlugin<InspectorState> {
expanded: expand, expanded: expand,
}, },
}, },
AXelements: {
...state.AXelements,
[key]: {
...state.AXelements[key],
expanded: expand,
},
},
}; };
}, },
@@ -301,7 +276,7 @@ export default class Layout extends SonarPlugin<InspectorState> {
let updatedFocus = forFocusEvent ? null : state.AXfocused; let updatedFocus = forFocusEvent ? null : state.AXfocused;
for (const element of elements) { for (const element of elements) {
if (element.extraInfo.focused) { if (element.extraInfo && element.extraInfo.focused) {
updatedFocus = element.id; updatedFocus = element.id;
} }
const current = updatedElements[element.id] || {}; const current = updatedElements[element.id] || {};
@@ -447,18 +422,12 @@ export default class Layout extends SonarPlugin<InspectorState> {
return elements; return elements;
} }
init() { axEnabled(): boolean {
performance.mark('LayoutInspectorInitialize'); // only visible internally for Android clients
this.client.call('getRoot').then((element: Element) => { return AXToggleButtonEnabled && this.realClient.query.os === 'Android';
this.dispatchAction({elements: [element], type: 'UpdateElements'}); }
this.dispatchAction({root: element.id, type: 'SetRoot'});
this.performInitialExpand(element, false).then(() => {
this.props.logger.trackTimeSince('LayoutInspectorInitialize');
this.setState({initialised: true});
});
});
if (AXToggleButtonEnabled) { initAX() {
this.client.call('getAXRoot').then((element: Element) => { this.client.call('getAXRoot').then((element: Element) => {
this.dispatchAction({elements: [element], type: 'UpdateAXElements'}); this.dispatchAction({elements: [element], type: 'UpdateAXElements'});
this.dispatchAction({root: element.id, type: 'SetAXRoot'}); this.dispatchAction({root: element.id, type: 'SetAXRoot'});
@@ -466,15 +435,11 @@ export default class Layout extends SonarPlugin<InspectorState> {
this.setState({AXinitialised: true}); this.setState({AXinitialised: true});
}); });
}); });
}
this.client.subscribe('axFocusEvent', (focusEvent: AXFocusEventResult) => { this.client.subscribe('axFocusEvent', (focusEvent: AXFocusEventResult) => {
if (AXToggleButtonEnabled) {
// if focusing, need to update all elements in the tree because // if focusing, need to update all elements in the tree because
// we don't know which one now has focus // we don't know which one now has focus
const keys = focusEvent.isFocus const keys = focusEvent.isFocus ? Object.keys(this.state.AXelements) : [];
? Object.keys(this.state.AXelements)
: [];
// if unfocusing and currently focused element exists, update only the // if unfocusing and currently focused element exists, update only the
// focused element (and only if it is loaded in tree) // focused element (and only if it is loaded in tree)
@@ -486,14 +451,27 @@ export default class Layout extends SonarPlugin<InspectorState> {
keys.push(this.state.AXfocused); keys.push(this.state.AXfocused);
} }
this.getNodes(keys, true, true).then((elements: Array<Element>) => { this.getNodes(keys, {force: true, ax: true}).then(
(elements: Array<Element>) => {
this.dispatchAction({ this.dispatchAction({
elements, elements,
forFocusEvent: true, forFocusEvent: true,
type: 'UpdateAXElements', type: 'UpdateAXElements',
}); });
},
);
}); });
} }
init() {
performance.mark('LayoutInspectorInitialize');
this.client.call('getRoot').then((element: Element) => {
this.dispatchAction({elements: [element], type: 'UpdateElements'});
this.dispatchAction({root: element.id, type: 'SetRoot'});
this.performInitialExpand(element, false).then(() => {
this.props.logger.trackTimeSince('LayoutInspectorInitialize');
this.setState({initialised: true});
});
}); });
this.client.subscribe( this.client.subscribe(
@@ -530,6 +508,10 @@ export default class Layout extends SonarPlugin<InspectorState> {
}, },
); );
}); });
if (this.axEnabled()) {
this.initAX();
}
} }
invalidate(ids: Array<ElementID>): Promise<Array<Element>> { invalidate(ids: Array<ElementID>): Promise<Array<Element>> {
@@ -538,7 +520,8 @@ export default class Layout extends SonarPlugin<InspectorState> {
} }
const ax = this.state.inAXMode; const ax = this.state.inAXMode;
return this.getNodes(ids, true, ax).then((elements: Array<Element>) => { return this.getNodes(ids, {force: true, ax}).then(
(elements: Array<Element>) => {
const children = elements const children = elements
.filter(element => { .filter(element => {
const prev = (ax ? this.state.AXelements : this.state.elements)[ const prev = (ax ? this.state.AXelements : this.state.elements)[
@@ -552,39 +535,42 @@ export default class Layout extends SonarPlugin<InspectorState> {
return Promise.all([elements, this.invalidate(children)]).then(arr => { return Promise.all([elements, this.invalidate(children)]).then(arr => {
return arr.reduce((acc, val) => acc.concat(val), []); return arr.reduce((acc, val) => acc.concat(val), []);
}); });
}); },
);
} }
getNodesAndDirectChildren( getNodesAndDirectChildren(
ids: Array<ElementID>, ids: Array<ElementID>,
ax: boolean, // always false at the moment bc only used for select ax: boolean, // always false at the moment bc only used for select
): Promise<Array<Element>> { ): Promise<Array<Element>> {
return this.getNodes(ids, false, ax).then((elements: Array<Element>) => { return this.getNodes(ids, {force: false, ax}).then(
(elements: Array<Element>) => {
const children = elements const children = elements
.map(element => element.children) .map(element => element.children)
.reduce((acc, val) => acc.concat(val), []); .reduce((acc, val) => acc.concat(val), []);
return Promise.all([elements, this.getNodes(children, false, ax)]).then( return Promise.all([
arr => { elements,
this.getNodes(children, {force: false, ax}),
]).then(arr => {
return arr.reduce((acc, val) => acc.concat(val), []); return arr.reduce((acc, val) => acc.concat(val), []);
});
}, },
); );
});
} }
getChildren(key: ElementID, ax: boolean): Promise<Array<Element>> { getChildren(key: ElementID, ax: boolean): Promise<Array<Element>> {
return this.getNodes( return this.getNodes(
(ax ? this.state.AXelements : this.state.elements)[key].children, (ax ? this.state.AXelements : this.state.elements)[key].children,
false, {force: false, ax},
ax,
); );
} }
getNodes( getNodes(
ids: Array<ElementID> = [], ids: Array<ElementID> = [],
force: boolean, options: GetNodesOptions,
ax: boolean,
): Promise<Array<Element>> { ): Promise<Array<Element>> {
const {force, ax} = options;
if (!force) { if (!force) {
ids = ids.filter(id => { ids = ids.filter(id => {
return ( return (
@@ -639,7 +625,7 @@ export default class Layout extends SonarPlugin<InspectorState> {
if (this.state.inAXMode && !ax) { if (this.state.inAXMode && !ax) {
// expand child wrapper elements that aren't in the AX tree (e.g. fragments) // expand child wrapper elements that aren't in the AX tree (e.g. fragments)
for (const childElem of elements) { for (const childElem of elements) {
if (childElem.extraInfo.nonAXWithAXChild) { if (childElem.extraInfo && childElem.extraInfo.nonAXWithAXChild) {
this.setElementExpanded(childElem.id, true, false); this.setElementExpanded(childElem.id, true, false);
} }
} }
@@ -707,15 +693,51 @@ export default class Layout extends SonarPlugin<InspectorState> {
}; };
onElementSelected = debounce((key: ElementID) => { onElementSelected = debounce((key: ElementID) => {
this.dispatchAction({key, type: 'SelectElement'}); let finalKey = key;
let finalAXkey = null;
if (this.axEnabled()) {
const linkedAXNode =
this.state.elements[key] &&
this.state.elements[key].extraInfo &&
this.state.elements[key].extraInfo.linkedAXNode;
// element only in main tree with linkedAXNode selected
if (linkedAXNode) {
finalAXkey = linkedAXNode;
// element only in AX tree with linked nonAX (litho) element selected
} else if (
(!this.state.elements[key] ||
this.state.elements[key].name === 'ComponentHost') &&
this.state.AXtoNonAXMapping[key]
) {
finalKey = this.state.AXtoNonAXMapping[key];
finalAXkey = key;
// keys are same for both trees or 'linked' element does not exist
} else {
finalAXkey = key;
}
}
this.dispatchAction({
key: finalKey,
AXkey: finalAXkey,
type: 'SelectElement',
});
this.client.send('setHighlighted', {id: key}); this.client.send('setHighlighted', {id: key});
this.getNodes([key], true, false).then((elements: Array<Element>) => { this.getNodes([finalKey], {force: true, ax: false}).then(
(elements: Array<Element>) => {
this.dispatchAction({elements, type: 'UpdateElements'}); this.dispatchAction({elements, type: 'UpdateElements'});
}); },
if (AXToggleButtonEnabled) { );
this.getNodes([key], true, true).then((elements: Array<Element>) => { if (this.axEnabled() && finalAXkey) {
this.getNodes([finalAXkey], {force: true, ax: true}).then(
(elements: Array<Element>) => {
this.dispatchAction({elements, type: 'UpdateAXElements'}); this.dispatchAction({elements, type: 'UpdateAXElements'});
}); },
);
} }
}); });
@@ -795,7 +817,7 @@ export default class Layout extends SonarPlugin<InspectorState> {
} }
/> />
</SearchIconContainer> </SearchIconContainer>
{AXToggleButtonEnabled ? ( {this.axEnabled() ? (
<SearchIconContainer <SearchIconContainer
onClick={this.onToggleAccessibility} onClick={this.onToggleAccessibility}
role="button" role="button"

View File

@@ -39,7 +39,8 @@ const ElementsRowContainer = ContextMenu.extends(
return ''; return '';
} }
}, },
color: props => (props.selected ? colors.white : colors.grapeDark3), color: props =>
props.selected || props.focused ? colors.white : colors.grapeDark3,
flexShrink: 0, flexShrink: 0,
flexWrap: 'nowrap', flexWrap: 'nowrap',
height: ROW_HEIGHT, height: ROW_HEIGHT,
@@ -66,7 +67,7 @@ const ElementsRowContainer = ContextMenu.extends(
}, },
}, },
{ {
ignoreAttributes: ['level', 'selected', 'even'], ignoreAttributes: ['level', 'selected', 'even', 'focused'],
}, },
); );
@@ -132,9 +133,7 @@ class PartialHighlight extends PureComponent<{
static HighlightedText = styled.text({ static HighlightedText = styled.text({
backgroundColor: '#ffff33', backgroundColor: '#ffff33',
color: props => color: props =>
props.selected || props.focused props.selected ? `${colors.grapeDark3} !important` : 'auto',
? `${colors.grapeDark3} !important`
: 'auto',
}); });
render() { render() {
@@ -174,7 +173,6 @@ class ElementsRowAttribute extends PureComponent<{
value: string, value: string,
matchingSearchQuery: ?string, matchingSearchQuery: ?string,
selected: boolean, selected: boolean,
focused: boolean,
}> { }> {
render() { render() {
const {name, value, matchingSearchQuery, selected} = this.props; const {name, value, matchingSearchQuery, selected} = this.props;
@@ -296,7 +294,7 @@ class ElementsRow extends PureComponent<ElementsRowProps, ElementsRowState> {
<Glyph <Glyph
size={8} size={8}
name={element.expanded ? 'chevron-down' : 'chevron-right'} name={element.expanded ? 'chevron-down' : 'chevron-right'}
color={selected ? 'white' : colors.light80} color={selected || focused ? 'white' : colors.light80}
/> />
</span> </span>
); );