2021-01-30 07:33:04 +08:00
|
|
|
import monaco from "./live_editor/monaco";
|
|
|
|
import EditorClient from "./live_editor/editor_client";
|
|
|
|
import MonacoEditorAdapter from "./live_editor/monaco_editor_adapter";
|
|
|
|
import HookServerAdapter from "./live_editor/hook_server_adapter";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mounts cell source editor with real-time collaboration mechanism.
|
|
|
|
*/
|
|
|
|
class LiveEditor {
|
|
|
|
constructor(hook, container, cellId, type, source, revision) {
|
|
|
|
this.container = container;
|
|
|
|
this.cellId = cellId;
|
|
|
|
this.type = type;
|
|
|
|
this.source = source;
|
|
|
|
this._onChange = null;
|
2021-03-02 01:48:19 +08:00
|
|
|
this._onBlur = null;
|
2021-01-30 07:33:04 +08:00
|
|
|
|
|
|
|
this.__mountEditor();
|
|
|
|
|
|
|
|
const serverAdapter = new HookServerAdapter(hook, cellId);
|
|
|
|
const editorAdapter = new MonacoEditorAdapter(this.editor);
|
|
|
|
this.editorClient = new EditorClient(
|
|
|
|
serverAdapter,
|
|
|
|
editorAdapter,
|
|
|
|
revision
|
|
|
|
);
|
|
|
|
|
|
|
|
this.editorClient.onDelta((delta) => {
|
|
|
|
this.source = delta.applyToString(this.source);
|
|
|
|
this._onChange && this._onChange(this.source);
|
|
|
|
});
|
2021-03-02 01:48:19 +08:00
|
|
|
|
|
|
|
this.editor.onDidBlurEditorWidget(() => {
|
|
|
|
this._onBlur && this._onBlur();
|
|
|
|
});
|
2021-01-30 07:33:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a callback called with a new cell content whenever it changes.
|
|
|
|
*/
|
|
|
|
onChange(callback) {
|
|
|
|
this._onChange = callback;
|
|
|
|
}
|
|
|
|
|
2021-03-02 01:48:19 +08:00
|
|
|
/**
|
|
|
|
* Registers a callback called whenever the editor loses focus.
|
|
|
|
*/
|
|
|
|
onBlur(callback) {
|
|
|
|
this._onBlur = callback;
|
|
|
|
}
|
|
|
|
|
2021-01-30 07:33:04 +08:00
|
|
|
focus() {
|
|
|
|
this.editor.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
blur() {
|
|
|
|
if (this.editor.hasTextFocus()) {
|
|
|
|
document.activeElement.blur();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
__mountEditor() {
|
|
|
|
this.editor = monaco.editor.create(this.container, {
|
|
|
|
language: this.type,
|
|
|
|
value: this.source,
|
|
|
|
scrollbar: {
|
|
|
|
vertical: "hidden",
|
|
|
|
handleMouseWheel: false,
|
|
|
|
},
|
|
|
|
minimap: {
|
|
|
|
enabled: false,
|
|
|
|
},
|
|
|
|
overviewRulerLanes: 0,
|
|
|
|
scrollBeyondLastLine: false,
|
|
|
|
quickSuggestions: false,
|
|
|
|
renderIndentGuides: false,
|
|
|
|
occurrencesHighlight: false,
|
|
|
|
renderLineHighlight: "none",
|
|
|
|
theme: "custom",
|
|
|
|
});
|
|
|
|
|
|
|
|
this.editor.getModel().updateOptions({
|
|
|
|
tabSize: 2,
|
|
|
|
});
|
|
|
|
|
|
|
|
this.editor.updateOptions({
|
|
|
|
autoIndent: true,
|
|
|
|
tabSize: 2,
|
|
|
|
formatOnType: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
// Automatically adjust the editor size to fit the container.
|
|
|
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
|
|
entries.forEach((entry) => {
|
|
|
|
// Ignore hidden container.
|
|
|
|
if (this.container.offsetHeight > 0) {
|
|
|
|
this.editor.layout();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
resizeObserver.observe(this.container);
|
|
|
|
|
|
|
|
// Whenever editor content size changes (new line is added/removed)
|
|
|
|
// update the container height. Thanks to the above observer
|
|
|
|
// the editor is resized to fill the container.
|
|
|
|
// Related: https://github.com/microsoft/monaco-editor/issues/794#issuecomment-688959283
|
|
|
|
this.editor.onDidContentSizeChange(() => {
|
|
|
|
const contentHeight = this.editor.getContentHeight();
|
|
|
|
this.container.style.height = `${contentHeight}px`;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default LiveEditor;
|