import {json} from '@codemirror/lang-json'; import {StreamLanguage} from '@codemirror/language'; import {lua} from '@codemirror/legacy-modes/mode/lua'; import {EditorView, placeholder} from '@codemirror/view'; import {nord} from 'cm6-theme-nord'; import {basicSetup} from 'codemirror'; /** * Waits until the webpage loads and then it calls the * anonymous function, which calls main. */ window.onload = () => { main(); }; /** * Configures the code editors and populating published data buttons. */ function main() { const /** !HTMLElement */ scriptInput = document.getElementById('script-input'); const /** !HTMLElement */ publishedDataInput = document.getElementById('published-data-input'); const /** !HTMLElement */ savedStateInput = document.getElementById('saved-state-input'); // Adds a custom theme to the CodeMirrors. Makes it so that the CodeMirror // takes up the space of its parent container, defines the behavior of the // contents if they overflow past the parent container to add a scroll-bar and // customizes the scroll-bar appearence. const /** !Extension */ customTheme = EditorView.theme({ '&': { height: '100%', width: 'auto', }, '.cm-scroller': {overflow: 'auto'}, }); configureCodeMirror(scriptInput, [ basicSetup, nord, StreamLanguage.define(lua), placeholder(scriptInput.placeholder), customTheme, EditorView.lineWrapping, ]); const /** !EditorView */ publishedDataEditorView = configureCodeMirror(publishedDataInput, [ basicSetup, nord, json(), placeholder(publishedDataInput.placeholder), customTheme, EditorView.lineWrapping, ]); configureCodeMirror(savedStateInput, [ basicSetup, nord, json(), placeholder(savedStateInput.placeholder), customTheme, EditorView.lineWrapping, ]); setupPublishedDataButtonGroup(publishedDataEditorView); } /** * Adds a CodeMirror with the given extensions in place of the textArea. * Additionally links the input of the CodeMirror with the textArea. * @param {!HTMLTextAreaElement} textArea The TextArea that is replaced with a * CodeMirror. * @param {!Array<!Extension>} extensions Extensions to add to the configuration * of the CodeMirror. Possible extensions are here * https://codemirror.net/docs/extensions/. * @return {!EditorView} The newly configured EditorView. */ function configureCodeMirror(textArea, extensions) { const /** !EditorView */ editor = new EditorView({doc: textArea.value, extensions: extensions}); // Replaces textArea with code editor. textArea.parentNode.insertBefore(editor.dom, textArea); // The code editor and textArea have no reference of each other, // so value must be supplied from the editor when submitting. textArea.form.addEventListener('submit', () => { textArea.value = editor.state.doc.toString(); }); return editor; } /** * Populates the Published Data button group with buttons corresponding to the * available published data types. * * @param {!EditorView} publishedDataEditorView EditorView for the published * data. */ function setupPublishedDataButtonGroup(publishedDataEditorView) { fetch('/get_published_data_file_names_and_content', {method: 'POST'}) .then((response) => { // json() is necessary here to transform the response to an object // usable by Javascript. return response.json(); }) .then((responseObject) => { const /** !Array<string> */ fileNames = responseObject['file_names']; const /** !HTMLElement */ buttonGroup = document.getElementById('published-data-button-group'); for (const [i, fileName] of fileNames.entries()) { buttonGroup.appendChild(createPopulatingDataButton( fileName, publishedDataEditorView, responseObject[fileName])); if (i !== fileNames.length - 1) { const /** !HTMLSpanElement */ separator = document.createElement('span'); separator.classList.add('mdc-theme--on-surface'); separator.textContent = '·'; buttonGroup.appendChild(separator); } } }); } /** * Creates a button that replaces the contents of the * given EditorView on click with the data passed in. * * Each button follows a similar layout in HTML: * <button type="button" class="mdc-button"> * <span class="mdc-button__ripple"></span> * <span class="mdc-button__label">memory_publisher</span> * </button> * * @param {string} label The label of the button. * @param {!EditorView} editorView EditorView that represents the * target of the data. * @param {string} newContent The data used to replace contents of EditorView. * @return {!HTMLButtonElement} The newly created button. */ function createPopulatingDataButton(label, editorView, newContent) { const /** !HTMLButtonElement */ button = document.createElement('button'); button.type = 'button'; button.classList.add('mdc-button'); // Material design component that creates ripple effect onClick. const /** !HTMLSpanElement */ rippleSpan = document.createElement('span'); rippleSpan.classList.add('mdc-button__ripple'); const /** !HTMLSpanElement */ labelSpan = document.createElement('span'); labelSpan.textContent = label; button.appendChild(rippleSpan); button.appendChild(labelSpan); button.addEventListener('click', () => { editorView.dispatch({ changes: { from: 0, to: editorView.state.doc.length, insert: newContent, }, }); }) return button; }