AnimationTarget.js (5077B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 Component, 9 } = require("resource://devtools/client/shared/vendor/react.mjs"); 10 const { 11 connect, 12 } = require("resource://devtools/client/shared/vendor/react-redux.js"); 13 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 14 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 15 const { 16 translateNodeFrontToGrip, 17 } = require("resource://devtools/client/inspector/shared/utils.js"); 18 19 const { REPS, MODE } = ChromeUtils.importESModule( 20 "resource://devtools/client/shared/components/reps/index.mjs" 21 ); 22 const { Rep } = REPS; 23 const ElementNode = REPS.ElementNode; 24 25 const { 26 getInspectorStr, 27 } = require("resource://devtools/client/inspector/animation/utils/l10n.js"); 28 29 const { 30 highlightNode, 31 unhighlightNode, 32 } = require("resource://devtools/client/inspector/boxmodel/actions/box-model-highlighter.js"); 33 34 class AnimationTarget extends Component { 35 static get propTypes() { 36 return { 37 animation: PropTypes.object.isRequired, 38 dispatch: PropTypes.func.isRequired, 39 getNodeFromActor: PropTypes.func.isRequired, 40 highlightedNode: PropTypes.string.isRequired, 41 setHighlightedNode: PropTypes.func.isRequired, 42 setSelectedNode: PropTypes.func.isRequired, 43 }; 44 } 45 46 constructor(props) { 47 super(props); 48 49 this.state = { 50 nodeFront: null, 51 }; 52 } 53 54 // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 55 UNSAFE_componentWillMount() { 56 this.updateNodeFront(this.props.animation); 57 } 58 59 // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 60 UNSAFE_componentWillReceiveProps(nextProps) { 61 if (this.props.animation.actorID !== nextProps.animation.actorID) { 62 this.updateNodeFront(nextProps.animation); 63 } 64 } 65 66 shouldComponentUpdate(nextProps, nextState) { 67 return ( 68 this.state.nodeFront !== nextState.nodeFront || 69 this.props.highlightedNode !== nextState.highlightedNode 70 ); 71 } 72 73 async updateNodeFront(animation) { 74 const { getNodeFromActor } = this.props; 75 76 // Try and get it from the playerFront directly. 77 let nodeFront = animation.animationTargetNodeFront; 78 79 // Next, get it from the walkerActor if it wasn't found. 80 if (!nodeFront) { 81 try { 82 nodeFront = await getNodeFromActor(animation.actorID); 83 } catch (e) { 84 // If an error occured while getting the nodeFront and if it can't be 85 // attributed to the panel having been destroyed in the meantime, this 86 // error needs to be logged and render needs to stop. 87 console.error(e); 88 this.setState({ nodeFront: null }); 89 return; 90 } 91 } 92 93 this.setState({ nodeFront }); 94 } 95 96 async ensureNodeFront() { 97 if (!this.state.nodeFront.actorID) { 98 // In case of no actorID, the node front had been destroyed. 99 // This will occur when the pseudo element was re-generated. 100 await this.updateNodeFront(this.props.animation); 101 } 102 } 103 104 async highlight() { 105 await this.ensureNodeFront(); 106 107 if (this.state.nodeFront) { 108 this.props.dispatch( 109 highlightNode(this.state.nodeFront, { 110 hideInfoBar: true, 111 hideGuides: true, 112 }) 113 ); 114 } 115 } 116 117 async select() { 118 await this.ensureNodeFront(); 119 120 if (this.state.nodeFront) { 121 this.props.setSelectedNode(this.state.nodeFront); 122 } 123 } 124 125 render() { 126 const { dispatch, highlightedNode, setHighlightedNode } = this.props; 127 const { nodeFront } = this.state; 128 129 if (!nodeFront) { 130 return dom.div({ 131 className: "animation-target", 132 }); 133 } 134 135 const isHighlighted = nodeFront.actorID === highlightedNode; 136 137 return dom.div( 138 { 139 className: "animation-target" + (isHighlighted ? " highlighting" : ""), 140 }, 141 Rep({ 142 defaultRep: ElementNode, 143 mode: MODE.TINY, 144 inspectIconTitle: getInspectorStr( 145 "inspector.nodePreview.highlightNodeLabel" 146 ), 147 inspectIconClassName: "highlight-node", 148 object: translateNodeFrontToGrip(nodeFront), 149 onDOMNodeClick: () => this.select(), 150 onDOMNodeMouseOut: () => { 151 if (!isHighlighted) { 152 dispatch(unhighlightNode()); 153 } 154 }, 155 onDOMNodeMouseOver: () => { 156 if (!isHighlighted) { 157 this.highlight(); 158 } 159 }, 160 onInspectIconClick: (_, e) => { 161 e.stopPropagation(); 162 163 if (!isHighlighted) { 164 // At first, hide highlighter which was created by onDOMNodeMouseOver. 165 dispatch(unhighlightNode()); 166 } 167 168 setHighlightedNode(isHighlighted ? null : nodeFront); 169 }, 170 }) 171 ); 172 } 173 } 174 175 const mapStateToProps = state => { 176 return { 177 highlightedNode: state.animations.highlightedNode, 178 }; 179 }; 180 181 module.exports = connect(mapStateToProps)(AnimationTarget);