1import {json} from '@codemirror/lang-json'; 2import {StreamLanguage} from '@codemirror/language'; 3import {lua} from '@codemirror/legacy-modes/mode/lua'; 4import {EditorView, placeholder} from '@codemirror/view'; 5import {nord} from 'cm6-theme-nord'; 6import {basicSetup} from 'codemirror'; 7 8/** 9 * Waits until the webpage loads and then it calls the 10 * anonymous function, which calls main. 11 */ 12window.onload = () => { 13 main(); 14}; 15 16/** 17 * Configures the code editors and populating published data buttons. 18 */ 19function main() { 20 const /** !HTMLElement */ scriptInput = 21 document.getElementById('script-input'); 22 23 const /** !HTMLElement */ publishedDataInput = 24 document.getElementById('published-data-input'); 25 26 const /** !HTMLElement */ savedStateInput = 27 document.getElementById('saved-state-input'); 28 29 // Adds a custom theme to the CodeMirrors. Makes it so that the CodeMirror 30 // takes up the space of its parent container, defines the behavior of the 31 // contents if they overflow past the parent container to add a scroll-bar and 32 // customizes the scroll-bar appearence. 33 const /** !Extension */ customTheme = EditorView.theme({ 34 '&': { 35 height: '100%', 36 width: 'auto', 37 }, 38 '.cm-scroller': {overflow: 'auto'}, 39 }); 40 41 configureCodeMirror(scriptInput, [ 42 basicSetup, 43 nord, 44 StreamLanguage.define(lua), 45 placeholder(scriptInput.placeholder), 46 customTheme, 47 EditorView.lineWrapping, 48 ]); 49 50 const /** !EditorView */ publishedDataEditorView = 51 configureCodeMirror(publishedDataInput, [ 52 basicSetup, 53 nord, 54 json(), 55 placeholder(publishedDataInput.placeholder), 56 customTheme, 57 EditorView.lineWrapping, 58 ]); 59 60 configureCodeMirror(savedStateInput, [ 61 basicSetup, 62 nord, 63 json(), 64 placeholder(savedStateInput.placeholder), 65 customTheme, 66 EditorView.lineWrapping, 67 ]); 68 69 setupPublishedDataButtonGroup(publishedDataEditorView); 70} 71 72/** 73 * Adds a CodeMirror with the given extensions in place of the textArea. 74 * Additionally links the input of the CodeMirror with the textArea. 75 * @param {!HTMLTextAreaElement} textArea The TextArea that is replaced with a 76 * CodeMirror. 77 * @param {!Array<!Extension>} extensions Extensions to add to the configuration 78 * of the CodeMirror. Possible extensions are here 79 * https://codemirror.net/docs/extensions/. 80 * @return {!EditorView} The newly configured EditorView. 81 */ 82function configureCodeMirror(textArea, extensions) { 83 const /** !EditorView */ editor = 84 new EditorView({doc: textArea.value, extensions: extensions}); 85 86 // Replaces textArea with code editor. 87 textArea.parentNode.insertBefore(editor.dom, textArea); 88 89 // The code editor and textArea have no reference of each other, 90 // so value must be supplied from the editor when submitting. 91 textArea.form.addEventListener('submit', () => { 92 textArea.value = editor.state.doc.toString(); 93 }); 94 95 return editor; 96} 97 98/** 99 * Populates the Published Data button group with buttons corresponding to the 100 * available published data types. 101 * 102 * @param {!EditorView} publishedDataEditorView EditorView for the published 103 * data. 104 */ 105function setupPublishedDataButtonGroup(publishedDataEditorView) { 106 fetch('/get_published_data_file_names_and_content', {method: 'POST'}) 107 .then((response) => { 108 // json() is necessary here to transform the response to an object 109 // usable by Javascript. 110 return response.json(); 111 }) 112 .then((responseObject) => { 113 const /** !Array<string> */ fileNames = responseObject['file_names']; 114 const /** !HTMLElement */ buttonGroup = 115 document.getElementById('published-data-button-group'); 116 117 for (const [i, fileName] of fileNames.entries()) { 118 buttonGroup.appendChild(createPopulatingDataButton( 119 fileName, publishedDataEditorView, responseObject[fileName])); 120 121 if (i !== fileNames.length - 1) { 122 const /** !HTMLSpanElement */ separator = 123 document.createElement('span'); 124 separator.classList.add('mdc-theme--on-surface'); 125 separator.textContent = '·'; 126 buttonGroup.appendChild(separator); 127 } 128 } 129 }); 130} 131 132/** 133 * Creates a button that replaces the contents of the 134 * given EditorView on click with the data passed in. 135 * 136 * Each button follows a similar layout in HTML: 137 * <button type="button" class="mdc-button"> 138 * <span class="mdc-button__ripple"></span> 139 * <span class="mdc-button__label">memory_publisher</span> 140 * </button> 141 * 142 * @param {string} label The label of the button. 143 * @param {!EditorView} editorView EditorView that represents the 144 * target of the data. 145 * @param {string} newContent The data used to replace contents of EditorView. 146 * @return {!HTMLButtonElement} The newly created button. 147 */ 148function createPopulatingDataButton(label, editorView, newContent) { 149 const /** !HTMLButtonElement */ button = document.createElement('button'); 150 button.type = 'button'; 151 button.classList.add('mdc-button'); 152 153 // Material design component that creates ripple effect onClick. 154 const /** !HTMLSpanElement */ rippleSpan = document.createElement('span'); 155 rippleSpan.classList.add('mdc-button__ripple'); 156 157 const /** !HTMLSpanElement */ labelSpan = document.createElement('span'); 158 labelSpan.textContent = label; 159 button.appendChild(rippleSpan); 160 button.appendChild(labelSpan); 161 162 button.addEventListener('click', () => { 163 editorView.dispatch({ 164 changes: { 165 from: 0, 166 to: editorView.state.doc.length, 167 insert: newContent, 168 }, 169 }); 170 }) 171 172 return button; 173} 174