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