SourcePreview.js (4954B)
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 const { 7 Component, 8 } = require("resource://devtools/client/shared/vendor/react.mjs"); 9 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 10 const { 11 connect, 12 } = require("resource://devtools/client/shared/vendor/react-redux.js"); 13 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 14 const Editor = require("resource://devtools/client/shared/sourceeditor/editor.js"); 15 const { 16 setTargetSearchResult, 17 } = require("resource://devtools/client/netmonitor/src/actions/search.js"); 18 const { div } = dom; 19 /** 20 * CodeMirror editor as a React component 21 */ 22 class SourcePreview extends Component { 23 static get propTypes() { 24 return { 25 // Source editor syntax highlight mimeType, which is a mime type defined in CodeMirror 26 mimeType: PropTypes.string, 27 // Source editor content 28 text: PropTypes.string, 29 // Search result text to select 30 targetSearchResult: PropTypes.object, 31 // Reset target search result that has been used for navigation in this panel. 32 // This is done to avoid second navigation the next time. 33 resetTargetSearchResult: PropTypes.func, 34 url: PropTypes.string, 35 }; 36 } 37 38 componentDidMount() { 39 this.loadEditor(); 40 this.updateEditor(); 41 } 42 43 shouldComponentUpdate(nextProps) { 44 return ( 45 nextProps.mimeType !== this.props.mimeType || 46 nextProps.text !== this.props.text || 47 nextProps.targetSearchResult !== this.props.targetSearchResult 48 ); 49 } 50 51 componentDidUpdate(prevProps) { 52 const { targetSearchResult, text } = this.props; 53 if (prevProps.text !== text) { 54 // When updating from editor to editor 55 this.updateEditor(); 56 } else if (prevProps.targetSearchResult !== targetSearchResult) { 57 this.findSearchResult(); 58 } 59 } 60 61 componentWillUnmount() { 62 this.unloadEditor(); 63 } 64 65 getSourceEditorModeForMimetype(mimeType) { 66 const lang = mimeType.split("/")[1]; 67 return Editor.modes[lang]; 68 } 69 70 loadEditor() { 71 this.editor = new Editor({ 72 cm6: true, 73 lineNumbers: true, 74 lineWrapping: false, 75 disableSearchAddon: false, 76 useSearchAddonPanel: true, 77 mode: null, // Disable auto syntax detection, but then we set the mode later 78 readOnly: true, 79 theme: "mozilla", 80 value: "", 81 }); 82 83 this.editor.appendToLocalElement(this.refs.editorElement); 84 // Used for netmonitor tests 85 window.codeMirrorSourceEditorTestInstance = this.editor; 86 } 87 88 async updateEditor() { 89 const { mimeType, text, url } = this.props; 90 if (this?.editor?.hasCodeMirror) { 91 const mode = this.getSourceEditorModeForMimetype(mimeType); 92 await this.editor.setMode(mode); 93 await this.editor.setText(text, { documentId: url }); 94 // When navigating from the netmonitor search, find and highlight the 95 // the current search result. 96 await this.findSearchResult(); 97 } 98 } 99 100 unloadEditor() { 101 if (this.editor) { 102 this.editor.destroy(); 103 this.editor = null; 104 } 105 } 106 107 async findSearchResult() { 108 const { targetSearchResult, resetTargetSearchResult } = this.props; 109 if (targetSearchResult?.line) { 110 const { line } = targetSearchResult; 111 // scroll the editor to center the line 112 // with the target search result 113 if (this.editor) { 114 await this.editor.setCursorAt(line, 0); 115 116 // Highlight line 117 this.editor.setLineContentMarker({ 118 id: this.editor.markerTypes.HIGHLIGHT_LINE_MARKER, 119 lineClassName: "highlight-line", 120 lines: [{ line }], 121 }); 122 this.clearHighlightLineAfterDuration(); 123 } 124 } 125 126 resetTargetSearchResult(); 127 } 128 129 clearHighlightLineAfterDuration() { 130 const editorContainer = document.querySelector(".editor-row-container"); 131 132 if (editorContainer === null) { 133 return; 134 } 135 136 const duration = parseInt( 137 getComputedStyle(editorContainer).getPropertyValue( 138 "--highlight-line-duration" 139 ), 140 10 141 ); 142 143 const highlightTimeout = setTimeout(() => { 144 if (!this.editor) { 145 return; 146 } 147 clearTimeout(highlightTimeout); 148 this.editor.removeLineContentMarker("highlight-line-marker"); 149 }, duration); 150 } 151 152 render() { 153 return div( 154 { className: "editor-row-container" }, 155 div({ 156 ref: "editorElement", 157 className: "source-editor-mount devtools-monospace", 158 }) 159 ); 160 } 161 } 162 163 module.exports = connect( 164 state => { 165 if (!state.search) { 166 return null; 167 } 168 return { 169 targetSearchResult: state.search.targetSearchResult, 170 }; 171 }, 172 dispatch => ({ 173 resetTargetSearchResult: () => dispatch(setTargetSearchResult(null)), 174 }) 175 )(SourcePreview);