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:
committed by
Facebook Github Bot
parent
06e70a1555
commit
30a19901ee
@@ -35,23 +35,25 @@ import debounce from 'lodash.debounce';
|
||||
|
||||
export type InspectorState = {|
|
||||
initialised: boolean,
|
||||
AXinitialised: boolean,
|
||||
selected: ?ElementID,
|
||||
AXselected: ?ElementID,
|
||||
AXfocused: ?ElementID,
|
||||
root: ?ElementID,
|
||||
AXroot: ?ElementID,
|
||||
elements: {[key: ElementID]: Element},
|
||||
AXelements: {[key: ElementID]: Element},
|
||||
isSearchActive: boolean,
|
||||
inAXMode: boolean,
|
||||
searchResults: ?ElementSearchResultSet,
|
||||
outstandingSearchQuery: ?string,
|
||||
// properties for ax mode
|
||||
AXinitialised: boolean,
|
||||
AXselected: ?ElementID,
|
||||
AXfocused: ?ElementID,
|
||||
AXroot: ?ElementID,
|
||||
AXelements: {[key: ElementID]: Element},
|
||||
inAXMode: boolean,
|
||||
AXtoNonAXMapping: {[key: ElementID]: ElementID},
|
||||
|};
|
||||
|
||||
type SelectElementArgs = {|
|
||||
key: ElementID,
|
||||
AXkey: ElementID,
|
||||
|};
|
||||
|
||||
type ExpandElementArgs = {|
|
||||
@@ -84,6 +86,11 @@ type GetNodesResult = {|
|
||||
elements: Array<Element>,
|
||||
|};
|
||||
|
||||
type GetNodesOptions = {|
|
||||
force: boolean,
|
||||
ax: boolean,
|
||||
|};
|
||||
|
||||
type SearchResultTree = {|
|
||||
id: string,
|
||||
isMatch: Boolean,
|
||||
@@ -160,53 +167,28 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
||||
|
||||
state = {
|
||||
elements: {},
|
||||
AXelements: {},
|
||||
initialised: false,
|
||||
AXinitialised: false,
|
||||
isSearchActive: false,
|
||||
inAXMode: false,
|
||||
root: null,
|
||||
AXroot: null,
|
||||
selected: null,
|
||||
AXselected: null,
|
||||
AXfocused: null,
|
||||
searchResults: null,
|
||||
outstandingSearchQuery: null,
|
||||
// properties for ax mode
|
||||
inAXMode: false,
|
||||
AXelements: {},
|
||||
AXinitialised: false,
|
||||
AXroot: null,
|
||||
AXselected: null,
|
||||
AXfocused: null,
|
||||
AXtoNonAXMapping: {},
|
||||
};
|
||||
|
||||
reducers = {
|
||||
SelectElement(state: InspectorState, {key}: 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 {
|
||||
selected: key,
|
||||
AXselected: linkedAXNode,
|
||||
};
|
||||
|
||||
// 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,
|
||||
};
|
||||
}
|
||||
SelectElement(state: InspectorState, {key, AXkey}: SelectElementArgs) {
|
||||
return {
|
||||
selected: key,
|
||||
AXselected: AXkey,
|
||||
};
|
||||
},
|
||||
|
||||
ExpandElement(state: InspectorState, {expand, key}: ExpandElementArgs) {
|
||||
@@ -218,13 +200,6 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
||||
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;
|
||||
|
||||
for (const element of elements) {
|
||||
if (element.extraInfo.focused) {
|
||||
if (element.extraInfo && element.extraInfo.focused) {
|
||||
updatedFocus = element.id;
|
||||
}
|
||||
const current = updatedElements[element.id] || {};
|
||||
@@ -447,6 +422,47 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
||||
return elements;
|
||||
}
|
||||
|
||||
axEnabled(): boolean {
|
||||
// only visible internally for Android clients
|
||||
return AXToggleButtonEnabled && this.realClient.query.os === 'Android';
|
||||
}
|
||||
|
||||
initAX() {
|
||||
this.client.call('getAXRoot').then((element: Element) => {
|
||||
this.dispatchAction({elements: [element], type: 'UpdateAXElements'});
|
||||
this.dispatchAction({root: element.id, type: 'SetAXRoot'});
|
||||
this.performInitialExpand(element, true).then(() => {
|
||||
this.setState({AXinitialised: true});
|
||||
});
|
||||
});
|
||||
|
||||
this.client.subscribe('axFocusEvent', (focusEvent: AXFocusEventResult) => {
|
||||
// if focusing, need to update all elements in the tree because
|
||||
// we don't know which one now has focus
|
||||
const keys = focusEvent.isFocus ? Object.keys(this.state.AXelements) : [];
|
||||
|
||||
// if unfocusing and currently focused element exists, update only the
|
||||
// focused element (and only if it is loaded in tree)
|
||||
if (
|
||||
!focusEvent.isFocus &&
|
||||
this.state.AXfocused &&
|
||||
this.state.AXelements[this.state.AXfocused]
|
||||
) {
|
||||
keys.push(this.state.AXfocused);
|
||||
}
|
||||
|
||||
this.getNodes(keys, {force: true, ax: true}).then(
|
||||
(elements: Array<Element>) => {
|
||||
this.dispatchAction({
|
||||
elements,
|
||||
forFocusEvent: true,
|
||||
type: 'UpdateAXElements',
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
performance.mark('LayoutInspectorInitialize');
|
||||
this.client.call('getRoot').then((element: Element) => {
|
||||
@@ -458,44 +474,6 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
||||
});
|
||||
});
|
||||
|
||||
if (AXToggleButtonEnabled) {
|
||||
this.client.call('getAXRoot').then((element: Element) => {
|
||||
this.dispatchAction({elements: [element], type: 'UpdateAXElements'});
|
||||
this.dispatchAction({root: element.id, type: 'SetAXRoot'});
|
||||
this.performInitialExpand(element, true).then(() => {
|
||||
this.setState({AXinitialised: true});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.client.subscribe('axFocusEvent', (focusEvent: AXFocusEventResult) => {
|
||||
if (AXToggleButtonEnabled) {
|
||||
// if focusing, need to update all elements in the tree because
|
||||
// we don't know which one now has focus
|
||||
const keys = focusEvent.isFocus
|
||||
? Object.keys(this.state.AXelements)
|
||||
: [];
|
||||
|
||||
// if unfocusing and currently focused element exists, update only the
|
||||
// focused element (and only if it is loaded in tree)
|
||||
if (
|
||||
!focusEvent.isFocus &&
|
||||
this.state.AXfocused &&
|
||||
this.state.AXelements[this.state.AXfocused]
|
||||
) {
|
||||
keys.push(this.state.AXfocused);
|
||||
}
|
||||
|
||||
this.getNodes(keys, true, true).then((elements: Array<Element>) => {
|
||||
this.dispatchAction({
|
||||
elements,
|
||||
forFocusEvent: true,
|
||||
type: 'UpdateAXElements',
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.client.subscribe(
|
||||
'invalidate',
|
||||
({nodes}: {nodes: Array<{id: ElementID}>}) => {
|
||||
@@ -530,6 +508,10 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (this.axEnabled()) {
|
||||
this.initAX();
|
||||
}
|
||||
}
|
||||
|
||||
invalidate(ids: Array<ElementID>): Promise<Array<Element>> {
|
||||
@@ -538,53 +520,57 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
||||
}
|
||||
|
||||
const ax = this.state.inAXMode;
|
||||
return this.getNodes(ids, true, ax).then((elements: Array<Element>) => {
|
||||
const children = elements
|
||||
.filter(element => {
|
||||
const prev = (ax ? this.state.AXelements : this.state.elements)[
|
||||
element.id
|
||||
];
|
||||
return prev && prev.expanded;
|
||||
})
|
||||
.map(element => element.children)
|
||||
.reduce((acc, val) => acc.concat(val), []);
|
||||
return this.getNodes(ids, {force: true, ax}).then(
|
||||
(elements: Array<Element>) => {
|
||||
const children = elements
|
||||
.filter(element => {
|
||||
const prev = (ax ? this.state.AXelements : this.state.elements)[
|
||||
element.id
|
||||
];
|
||||
return prev && prev.expanded;
|
||||
})
|
||||
.map(element => element.children)
|
||||
.reduce((acc, val) => acc.concat(val), []);
|
||||
|
||||
return Promise.all([elements, this.invalidate(children)]).then(arr => {
|
||||
return arr.reduce((acc, val) => acc.concat(val), []);
|
||||
});
|
||||
});
|
||||
return Promise.all([elements, this.invalidate(children)]).then(arr => {
|
||||
return arr.reduce((acc, val) => acc.concat(val), []);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
getNodesAndDirectChildren(
|
||||
ids: Array<ElementID>,
|
||||
ax: boolean, // always false at the moment bc only used for select
|
||||
): Promise<Array<Element>> {
|
||||
return this.getNodes(ids, false, ax).then((elements: Array<Element>) => {
|
||||
const children = elements
|
||||
.map(element => element.children)
|
||||
.reduce((acc, val) => acc.concat(val), []);
|
||||
return this.getNodes(ids, {force: false, ax}).then(
|
||||
(elements: Array<Element>) => {
|
||||
const children = elements
|
||||
.map(element => element.children)
|
||||
.reduce((acc, val) => acc.concat(val), []);
|
||||
|
||||
return Promise.all([elements, this.getNodes(children, false, ax)]).then(
|
||||
arr => {
|
||||
return Promise.all([
|
||||
elements,
|
||||
this.getNodes(children, {force: false, ax}),
|
||||
]).then(arr => {
|
||||
return arr.reduce((acc, val) => acc.concat(val), []);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
getChildren(key: ElementID, ax: boolean): Promise<Array<Element>> {
|
||||
return this.getNodes(
|
||||
(ax ? this.state.AXelements : this.state.elements)[key].children,
|
||||
false,
|
||||
ax,
|
||||
{force: false, ax},
|
||||
);
|
||||
}
|
||||
|
||||
getNodes(
|
||||
ids: Array<ElementID> = [],
|
||||
force: boolean,
|
||||
ax: boolean,
|
||||
options: GetNodesOptions,
|
||||
): Promise<Array<Element>> {
|
||||
const {force, ax} = options;
|
||||
if (!force) {
|
||||
ids = ids.filter(id => {
|
||||
return (
|
||||
@@ -639,7 +625,7 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
||||
if (this.state.inAXMode && !ax) {
|
||||
// expand child wrapper elements that aren't in the AX tree (e.g. fragments)
|
||||
for (const childElem of elements) {
|
||||
if (childElem.extraInfo.nonAXWithAXChild) {
|
||||
if (childElem.extraInfo && childElem.extraInfo.nonAXWithAXChild) {
|
||||
this.setElementExpanded(childElem.id, true, false);
|
||||
}
|
||||
}
|
||||
@@ -707,15 +693,51 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
||||
};
|
||||
|
||||
onElementSelected = debounce((key: ElementID) => {
|
||||
this.dispatchAction({key, type: 'SelectElement'});
|
||||
this.client.send('setHighlighted', {id: key});
|
||||
this.getNodes([key], true, false).then((elements: Array<Element>) => {
|
||||
this.dispatchAction({elements, type: 'UpdateElements'});
|
||||
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',
|
||||
});
|
||||
if (AXToggleButtonEnabled) {
|
||||
this.getNodes([key], true, true).then((elements: Array<Element>) => {
|
||||
this.dispatchAction({elements, type: 'UpdateAXElements'});
|
||||
});
|
||||
this.client.send('setHighlighted', {id: key});
|
||||
this.getNodes([finalKey], {force: true, ax: false}).then(
|
||||
(elements: Array<Element>) => {
|
||||
this.dispatchAction({elements, type: 'UpdateElements'});
|
||||
},
|
||||
);
|
||||
if (this.axEnabled() && finalAXkey) {
|
||||
this.getNodes([finalAXkey], {force: true, ax: true}).then(
|
||||
(elements: Array<Element>) => {
|
||||
this.dispatchAction({elements, type: 'UpdateAXElements'});
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -795,7 +817,7 @@ export default class Layout extends SonarPlugin<InspectorState> {
|
||||
}
|
||||
/>
|
||||
</SearchIconContainer>
|
||||
{AXToggleButtonEnabled ? (
|
||||
{this.axEnabled() ? (
|
||||
<SearchIconContainer
|
||||
onClick={this.onToggleAccessibility}
|
||||
role="button"
|
||||
|
||||
Reference in New Issue
Block a user