From 30a19901eeb80ed19b77012242c3273d07a8b0af Mon Sep 17 00:00:00 2001 From: Sara Valderrama Date: Wed, 1 Aug 2018 13:30:37 -0700 Subject: [PATCH] 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 --- src/plugins/layout/index.js | 274 ++++++++++-------- .../components/elements-inspector/elements.js | 12 +- 2 files changed, 153 insertions(+), 133 deletions(-) diff --git a/src/plugins/layout/index.js b/src/plugins/layout/index.js index e5e387a21..801b2b5fd 100644 --- a/src/plugins/layout/index.js +++ b/src/plugins/layout/index.js @@ -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, |}; +type GetNodesOptions = {| + force: boolean, + ax: boolean, +|}; + type SearchResultTree = {| id: string, isMatch: Boolean, @@ -160,53 +167,28 @@ export default class Layout extends SonarPlugin { 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 { expanded: expand, }, }, - AXelements: { - ...state.AXelements, - [key]: { - ...state.AXelements[key], - expanded: expand, - }, - }, }; }, @@ -301,7 +276,7 @@ export default class Layout extends SonarPlugin { 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 { 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) => { + 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 { }); }); - 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) => { - 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 { }, ); }); + + if (this.axEnabled()) { + this.initAX(); + } } invalidate(ids: Array): Promise> { @@ -538,53 +520,57 @@ export default class Layout extends SonarPlugin { } const ax = this.state.inAXMode; - return this.getNodes(ids, true, ax).then((elements: Array) => { - 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) => { + 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, ax: boolean, // always false at the moment bc only used for select ): Promise> { - return this.getNodes(ids, false, ax).then((elements: Array) => { - const children = elements - .map(element => element.children) - .reduce((acc, val) => acc.concat(val), []); + return this.getNodes(ids, {force: false, ax}).then( + (elements: Array) => { + 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> { return this.getNodes( (ax ? this.state.AXelements : this.state.elements)[key].children, - false, - ax, + {force: false, ax}, ); } getNodes( ids: Array = [], - force: boolean, - ax: boolean, + options: GetNodesOptions, ): Promise> { + const {force, ax} = options; if (!force) { ids = ids.filter(id => { return ( @@ -639,7 +625,7 @@ export default class Layout extends SonarPlugin { 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 { }; onElementSelected = debounce((key: ElementID) => { - this.dispatchAction({key, type: 'SelectElement'}); - this.client.send('setHighlighted', {id: key}); - this.getNodes([key], true, false).then((elements: Array) => { - 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) => { - this.dispatchAction({elements, type: 'UpdateAXElements'}); - }); + this.client.send('setHighlighted', {id: key}); + this.getNodes([finalKey], {force: true, ax: false}).then( + (elements: Array) => { + this.dispatchAction({elements, type: 'UpdateElements'}); + }, + ); + if (this.axEnabled() && finalAXkey) { + this.getNodes([finalAXkey], {force: true, ax: true}).then( + (elements: Array) => { + this.dispatchAction({elements, type: 'UpdateAXElements'}); + }, + ); } }); @@ -795,7 +817,7 @@ export default class Layout extends SonarPlugin { } /> - {AXToggleButtonEnabled ? ( + {this.axEnabled() ? ( (props.selected ? colors.white : colors.grapeDark3), + color: props => + props.selected || props.focused ? colors.white : colors.grapeDark3, flexShrink: 0, flexWrap: 'nowrap', 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({ backgroundColor: '#ffff33', color: props => - props.selected || props.focused - ? `${colors.grapeDark3} !important` - : 'auto', + props.selected ? `${colors.grapeDark3} !important` : 'auto', }); render() { @@ -174,7 +173,6 @@ class ElementsRowAttribute extends PureComponent<{ value: string, matchingSearchQuery: ?string, selected: boolean, - focused: boolean, }> { render() { const {name, value, matchingSearchQuery, selected} = this.props; @@ -296,7 +294,7 @@ class ElementsRow extends PureComponent { );