github.com/replicatedhq/ship@v0.55.0/web/init/src/components/kustomize/kustomize_overlay/AceEditorHOC.jsx (about) 1 import React from "react"; 2 import ace from "brace"; 3 import AceEditor from "react-ace"; 4 import * as ast from "yaml-ast-parser"; 5 import find from "lodash/find"; 6 7 const { addListener, removeListener } = ace.acequire("ace/lib/event"); 8 9 export const PATCH_TOKEN = "TO_BE_MODIFIED"; 10 11 export class AceEditorHOC extends React.Component { 12 constructor(props) { 13 super(props); 14 this.state = { 15 activeMarker: [], 16 markers: [], 17 }; 18 } 19 20 componentDidUpdate(prevProps) { 21 const { fileToView } = this.props; 22 if (fileToView !== prevProps.fileToView) { 23 if (fileToView.baseContent && fileToView.isSupported) { 24 const markers = this.createMarkers(fileToView); 25 this.setState({ markers }); 26 } 27 } 28 if ( 29 (this.props.overlayOpen !== prevProps.overlayOpen) || 30 (this.props.diffOpen !== prevProps.diffOpen) 31 ) { 32 if (this.aceEditorBase) { 33 this.aceEditorBase.editor.resize(); 34 } 35 } 36 } 37 38 componentDidMount() { 39 addListener(this.aceEditorBase.editor, "click", this.addToOverlay) 40 addListener(this.aceEditorBase.editor.renderer.scroller, "mousemove", this.setActiveMarker); 41 addListener(this.aceEditorBase.editor.renderer.scroller, "mouseout", this.setActiveMarker); 42 } 43 44 componentWillUnmount() { 45 removeListener(this.aceEditorBase.editor, "click", this.addToOverlay); 46 removeListener(this.aceEditorBase.editor.renderer.scroller, "mousemove", this.setActiveMarker); 47 removeListener(this.aceEditorBase.editor.renderer.scroller, "mouseout", this.setActiveMarker) 48 } 49 50 findMarkerAtRow = (row, markers) => ( 51 find(markers, ({ startRow, endRow }) => ( row >= startRow && row <= endRow )) 52 ) 53 54 addToOverlay = async () => { 55 const { handleGeneratePatch, handleApplyPatch } = this.props; 56 const { activeMarker } = this.state; 57 58 if (activeMarker.length > 0) { 59 const matchingMarker = activeMarker[0]; 60 const { path } = matchingMarker; 61 await handleApplyPatch().catch(); 62 handleGeneratePatch(path); 63 } 64 } 65 66 setActiveMarker = (e) => { 67 const { clientY } = e; 68 const { markers, activeMarker } = this.state; 69 70 const renderer = this.aceEditorBase.editor.renderer; 71 const canvasPos = renderer.scroller.getBoundingClientRect(); 72 73 const row = Math.ceil((clientY + renderer.scrollTop - canvasPos.top) / renderer.lineHeight); 74 const matchingMarker = this.findMarkerAtRow(row, markers); 75 76 if (matchingMarker) { 77 renderer.setCursorStyle("pointer"); 78 const [ activeMarker0 = {} ] = activeMarker; 79 if (matchingMarker.startRow !== activeMarker0.startRow) { 80 this.setState({ activeMarker: [ matchingMarker ] }); 81 } 82 } else { 83 this.setState({ activeMarker: [] }); 84 } 85 } 86 87 createMarkers = (fileToView) => { 88 if (this.aceEditorBase) { 89 let markers = []; 90 const loadedAst = ast.safeLoad(fileToView.baseContent, null); 91 this.createMarkersRec(loadedAst, [], markers); 92 return markers; 93 } 94 } 95 96 createMarkersRec = (ast, path, markers) => { 97 const aceDoc = this.aceEditorBase.editor.getSession().getDocument(); 98 99 const createMarkersSlice = ({ items }) => { 100 if (items && items.length > 0) { 101 for (let i = 0; i < items.length; i++) { 102 const item = items[i]; 103 this.createMarkersRec(item, [...path, i], markers); 104 } 105 } 106 }; 107 108 const createMarkersMap = ({ mappings }) => { 109 for (const mapping of mappings) { 110 const { value, key } = mapping; 111 if (value === null) { 112 const { startPosition, endPosition } = ast; 113 const { row: startRow } = aceDoc.indexToPosition(startPosition, 0); 114 const { row: endRow } = aceDoc.indexToPosition(endPosition, 0); 115 const nullMarker = { 116 startRow, 117 endRow: endRow + 1, 118 className: "marker-highlight-null", 119 mapping, 120 path: [...path, key.value], 121 } 122 return markers.push(nullMarker); 123 } 124 const newPathKey = key.value; 125 this.createMarkersRec(value, [...path, newPathKey], markers); 126 } 127 }; 128 129 if (ast.mappings) { 130 return createMarkersMap(ast); 131 } 132 if (ast.items) { 133 return createMarkersSlice(ast); 134 } 135 136 const { startPosition, endPosition } = ast; 137 const { row: startRow } = aceDoc.indexToPosition(startPosition, 0); 138 const { row: endRow } = aceDoc.indexToPosition(endPosition, 0); 139 const newMarker = { 140 startRow, 141 endRow: endRow + 1, 142 className: "marker-highlight", 143 mapping: ast, 144 path, 145 }; 146 markers.push(newMarker); 147 } 148 149 render() { 150 const { fileToView } = this.props; 151 const { activeMarker } = this.state; 152 153 return ( 154 <AceEditor 155 ref={(editor) => { this.aceEditorBase = editor }} 156 mode="yaml" 157 theme="chrome" 158 className="flex1 flex" 159 readOnly={true} 160 value={fileToView && fileToView.baseContent || ""} 161 height="100%" 162 width="100%" 163 editorProps={{ 164 $blockScrolling: Infinity, 165 useSoftTabs: true, 166 tabSize: 2, 167 }} 168 setOptions={{ 169 scrollPastEnd: false 170 }} 171 markers={activeMarker} 172 /> 173 ); 174 } 175 }