From 4e4973bd91b23828fd04fc2810209e422cfb7427 Mon Sep 17 00:00:00 2001
From: Kushagra Gour <chinchang457@gmail.com>
Date: Mon, 10 Dec 2018 23:05:11 +0530
Subject: [PATCH] add docs and fix setOption in CodeEditor

---
 preact.config.js              |  5 ++++
 src/components/CodeEditor.jsx | 50 ++++++++++++++++++++++++++++-------
 2 files changed, 45 insertions(+), 10 deletions(-)

diff --git a/preact.config.js b/preact.config.js
index bf6adbb..0f1779f 100644
--- a/preact.config.js
+++ b/preact.config.js
@@ -27,8 +27,13 @@ export default function(config, env, helpers) {
 
 	if (env.isProd) {
 		config.devtool = false; // disable sourcemaps
+
+		// To support chunk loading in root and also /app path
 		config.output.publicPath = './';
+
+		// Remove the default hash append in chunk name
 		config.output.chunkFilename = '[name].chunk.js';
+
 		config.plugins.push(
 			new CommonsChunkPlugin({
 				name: 'vendor',
diff --git a/src/components/CodeEditor.jsx b/src/components/CodeEditor.jsx
index dfb17c3..90079ee 100644
--- a/src/components/CodeEditor.jsx
+++ b/src/components/CodeEditor.jsx
@@ -34,9 +34,10 @@ import 'code-blast-codemirror/code-blast.js';
 import emmet from '@emmetio/codemirror-plugin';
 import { prettify, loadCss } from '../utils';
 import { modes } from '../codeModes';
+import { deferred } from '../deferred';
 
 emmet(CodeMirror);
-let isMonacoDepsLoaded = false;
+let monacoDepsDeferred;
 
 window.MonacoEnvironment = {
 	getWorkerUrl(moduleId, label) {
@@ -90,8 +91,8 @@ export default class CodeEditor extends Component {
 			}
 		}
 
+		// Only condition when this component updates is when prop.type changes
 		if (nextProps.type !== this.props.type) {
-			// debugger;
 			if (
 				this.node.parentElement.querySelector('.monaco-editor, .CodeMirror')
 			) {
@@ -106,6 +107,7 @@ export default class CodeEditor extends Component {
 		return false;
 	}
 	componentDidUpdate(prevProps) {
+		// prop.type changed, reinit the editor
 		this.initEditor();
 	}
 	setModel(model) {
@@ -114,6 +116,9 @@ export default class CodeEditor extends Component {
 			: this.instance.setModel(model);
 	}
 	setValue(value) {
+		// HACK: We set a flag on window for an ultra-short duration, which 'change'
+		// listener uses to set the change.origin to 'setValue', otherwise it's
+		// '+input'
 		if (this.props.type === 'monaco') {
 			window.monacoSetValTriggered = true;
 			setTimeout(() => {
@@ -121,6 +126,9 @@ export default class CodeEditor extends Component {
 			}, 1);
 		}
 		this.instance.setValue(value);
+		// We save last set value so that when editor type changes, we can
+		// populate that last value
+		this.lastSetValue = value;
 	}
 	getValue() {
 		return this.instance.getValue();
@@ -135,7 +143,15 @@ export default class CodeEditor extends Component {
 			this.instance.restoreViewState(state);
 		}
 	}
-	setOption(option, value) {}
+	setOption(option, value) {
+		if (this.props.type === 'monaco') {
+			this.monacoEditorReadyDeferred.promise.then(() => {
+				this.instance.updateOptions({ [option]: value });
+			});
+		} else {
+			this.instance.setOption(option, value);
+		}
+	}
 	setLanguage(value) {
 		if (!window.monaco) return;
 
@@ -179,6 +195,11 @@ export default class CodeEditor extends Component {
 		this.instance.focus();
 	}
 
+	/**
+	 * Converts codemirror mode value to monaco language values.
+	 * TODO: Refactor to not be codemirror related.
+	 * @param {string} mode Codemirror mode value
+	 */
 	getMonacoLanguageFromMode(mode) {
 		if (['htmlmixed'].includes(mode)) {
 			return 'html';
@@ -192,27 +213,34 @@ export default class CodeEditor extends Component {
 		return mode;
 	}
 
+	/**
+	 * Loads the asynchronous deps according to the editor type.
+	 */
 	async loadDeps() {
-		if (this.props.type === 'monaco' && !isMonacoDepsLoaded) {
-			if (!$('#monaco-css')) {
+		if (this.props.type === 'monaco') {
+			if (!monacoDepsDeferred) {
+				monacoDepsDeferred = deferred();
 				loadCss({ url: 'lib/monaco/monaco.css', id: 'monaco-css' });
+				import(/* webpackChunkName: "monaco" */ '../lib/monaco/monaco.bundle.js').then(
+					() => {
+						monacoDepsDeferred.resolve();
+					}
+				);
 			}
-			return import(/* webpackChunkName: "monaco" */ '../lib/monaco/monaco.bundle.js').then(
-				() => {
-					isMonacoDepsLoaded = true;
-				}
-			);
+			return monacoDepsDeferred.promise;
 		}
 		return Promise.resolve();
 	}
 
 	async initEditor() {
+		this.monacoEditorReadyDeferred = deferred();
 		await this.loadDeps();
 
 		const { options, prefs } = this.props;
 		if (this.props.type === 'monaco') {
 			this.instance = monaco.editor.create(this.node, {
 				language: this.getMonacoLanguageFromMode(options.mode),
+				value: this.lastSetValue || '',
 				roundedSelection: false,
 				scrollBeyondLastLine: false,
 				theme: 'vs-dark',
@@ -243,9 +271,11 @@ export default class CodeEditor extends Component {
 					}
 				}
 			);
+			this.monacoEditorReadyDeferred.resolve();
 		} else {
 			this.instance = CodeMirror.fromTextArea(this.node, {
 				mode: options.mode,
+				value: this.lastSetValue || '',
 				lineNumbers: true,
 				lineWrapping: !!prefs.lineWrap,
 				autofocus: options.autofocus || false,