index.js (4652B)
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 import PropTypes from "devtools/client/shared/vendor/react-prop-types"; 6 import React, { PureComponent } from "devtools/client/shared/vendor/react"; 7 import { connect } from "devtools/client/shared/vendor/react-redux"; 8 9 import Popup from "./Popup"; 10 11 import { 12 getIsCurrentThreadPaused, 13 getSelectedTraceIndex, 14 } from "../../../selectors/index"; 15 import actions from "../../../actions/index"; 16 17 const EXCEPTION_MARKER = "mark-text-exception"; 18 19 const LOADING_CLASS = "preview-loading-token"; 20 21 class Preview extends PureComponent { 22 target = null; 23 constructor(props) { 24 super(props); 25 this.state = { selecting: false, loading: null }; 26 } 27 28 static get propTypes() { 29 return { 30 editor: PropTypes.object.isRequired, 31 editorRef: PropTypes.object.isRequired, 32 isPaused: PropTypes.bool.isRequired, 33 hasSelectedTrace: PropTypes.bool.isRequired, 34 getExceptionPreview: PropTypes.func.isRequired, 35 getPreview: PropTypes.func, 36 }; 37 } 38 39 componentDidMount() { 40 this.props.editor.on("tokenenter", this.onTokenEnter); 41 this.props.editor.addEditorDOMEventListeners({ 42 mouseup: this.onMouseUp, 43 mousedown: this.onMouseDown, 44 scroll: this.onScroll, 45 }); 46 } 47 48 componentWillUnmount() { 49 this.props.editor.off("tokenenter", this.onTokenEnter); 50 this.props.editor.removeEditorDOMEventListeners({ 51 mouseup: this.onMouseUp, 52 mousedown: this.onMouseDown, 53 scroll: this.onScroll, 54 }); 55 } 56 57 componentDidUpdate(_prevProps, prevState) { 58 // Ensure that only one token is highlighted as "loading" 59 const previous = prevState.loading; 60 if (previous) { 61 previous.classList.remove(LOADING_CLASS); 62 } 63 const { loading } = this.state; 64 if (loading) { 65 loading.classList.add(LOADING_CLASS); 66 } 67 } 68 69 // Note that these events are emitted by utils/editor/tokens.js 70 onTokenEnter = async ({ target, tokenPos }) => { 71 // Use a temporary object to uniquely identify the asynchronous processing of this user event 72 // and bail out if we started hovering another token. 73 const tokenId = {}; 74 this.currentTokenId = tokenId; 75 76 // Immediately highlight the hovered token as "loading" 77 this.setState({ loading: target }); 78 79 const { 80 editor, 81 getPausedPreview, 82 getTracerPreview, 83 getExceptionPreview, 84 isPaused, 85 hasSelectedTrace, 86 } = this.props; 87 const isTargetException = target.closest(`.${EXCEPTION_MARKER}`); 88 89 let preview; 90 try { 91 if (isTargetException) { 92 preview = await getExceptionPreview(target, tokenPos, editor); 93 } 94 95 if (!preview && (hasSelectedTrace || isPaused) && !this.state.selecting) { 96 if (hasSelectedTrace) { 97 preview = await getTracerPreview(target, tokenPos, editor); 98 } 99 if (!preview && isPaused) { 100 preview = await getPausedPreview(target, tokenPos, editor); 101 } 102 } 103 } catch (e) { 104 // Ignore any exception and dismiss the popup (as preview will be null) 105 } 106 107 // Prevent modifying state and showing this preview if we started hovering another token 108 if (this.currentTokenId !== tokenId) { 109 return; 110 } 111 112 this.setState({ loading: null, preview }); 113 }; 114 115 onMouseUp = () => { 116 if (this.props.isPaused || this.props.hasSelectedTrace) { 117 this.setState({ selecting: false }); 118 } 119 }; 120 121 onMouseDown = () => { 122 if (this.props.isPaused || this.props.hasSelectedTrace) { 123 this.setState({ selecting: true }); 124 } 125 }; 126 127 onScroll = () => { 128 if (this.props.isPaused || this.props.hasSelectedTrace) { 129 this.clearPreview(); 130 } 131 }; 132 133 clearPreview = () => { 134 this.setState({ loading: null, preview: null }); 135 }; 136 137 render() { 138 if (this.state.selecting) { 139 return null; 140 } 141 142 const { preview } = this.state; 143 if (!preview) { 144 return null; 145 } 146 147 return React.createElement(Popup, { 148 preview, 149 editor: this.props.editor, 150 editorRef: this.props.editorRef, 151 clearPreview: this.clearPreview, 152 }); 153 } 154 } 155 156 const mapStateToProps = state => { 157 return { 158 isPaused: getIsCurrentThreadPaused(state), 159 hasSelectedTrace: getSelectedTraceIndex(state) != null, 160 }; 161 }; 162 163 export default connect(mapStateToProps, { 164 addExpression: actions.addExpression, 165 getPausedPreview: actions.getPausedPreview, 166 getTracerPreview: actions.getTracerPreview, 167 getExceptionPreview: actions.getExceptionPreview, 168 })(Preview);