mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-09-20 10:05:57 +08:00
Mount Markdown cell editor lazily (#1102)
This commit is contained in:
parent
a144d3d4fb
commit
aafdc12015
|
@ -156,6 +156,18 @@ const Cell = {
|
|||
|
||||
this.updateInsertModeAvailability();
|
||||
|
||||
if (this.props.type !== "markdown") {
|
||||
// For markdown cells the editor is mounted lazily when needed,
|
||||
// for other cells we mount the editor eagerly, however mounting
|
||||
// is a synchronous operation and is relatively expensive, so we
|
||||
// defer it to run after the current event handlers
|
||||
setTimeout(() => {
|
||||
if (!liveEditor.isMounted()) {
|
||||
liveEditor.mount();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (liveEditor === this.currentEditor()) {
|
||||
// Once the editor is created, reflect the current insert mode state
|
||||
this.maybeFocusCurrentEditor(true);
|
||||
|
|
|
@ -12,8 +12,6 @@ const CellEditor = {
|
|||
`[data-el-editor-container]`
|
||||
);
|
||||
|
||||
// Remove the content placeholder
|
||||
editorContainer.firstElementChild.remove();
|
||||
const editorEl = document.createElement("div");
|
||||
editorContainer.appendChild(editorEl);
|
||||
|
||||
|
@ -29,6 +27,11 @@ const CellEditor = {
|
|||
read_only
|
||||
);
|
||||
|
||||
this.liveEditor.onMount(() => {
|
||||
// Remove the content placeholder
|
||||
editorContainer.querySelector(`[data-el-skeleton]`).remove();
|
||||
});
|
||||
|
||||
this.el.dispatchEvent(
|
||||
new CustomEvent("lb:cell:editor_created", {
|
||||
detail: { tag: this.props.tag, liveEditor: this.liveEditor },
|
||||
|
|
|
@ -28,29 +28,43 @@ class LiveEditor {
|
|||
this.language = language;
|
||||
this.intellisense = intellisense;
|
||||
this.readOnly = readOnly;
|
||||
this._onMount = [];
|
||||
this._onChange = [];
|
||||
this._onBlur = [];
|
||||
this._onCursorSelectionChange = [];
|
||||
this._remoteUserByClientPid = {};
|
||||
|
||||
const serverAdapter = new HookServerAdapter(hook, cellId, tag);
|
||||
this.editorClient = new EditorClient(serverAdapter, revision);
|
||||
|
||||
this.editorClient.onDelta((delta) => {
|
||||
this.source = delta.applyToString(this.source);
|
||||
this._onChange.forEach((callback) => callback(this.source));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an editor instance has been mounted in the DOM.
|
||||
*/
|
||||
isMounted() {
|
||||
return !!this.editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mounts and configures an editor instance in the DOM.
|
||||
*/
|
||||
mount() {
|
||||
if (this.isMounted()) {
|
||||
throw new Error("The editor is already mounted");
|
||||
}
|
||||
|
||||
this._mountEditor();
|
||||
|
||||
if (this.intellisense) {
|
||||
this._setupIntellisense();
|
||||
}
|
||||
|
||||
const serverAdapter = new HookServerAdapter(hook, cellId, tag);
|
||||
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.forEach((callback) => callback(this.source));
|
||||
});
|
||||
this.editorClient.setEditorAdapter(new MonacoEditorAdapter(this.editor));
|
||||
|
||||
this.editor.onDidFocusEditorWidget(() => {
|
||||
this.editor.updateOptions({ matchBrackets: "always" });
|
||||
|
@ -66,6 +80,14 @@ class LiveEditor {
|
|||
callback(event.selection)
|
||||
);
|
||||
});
|
||||
|
||||
this._onMount.forEach((callback) => callback());
|
||||
}
|
||||
|
||||
_ensureMounted() {
|
||||
if (!this.isMounted()) {
|
||||
this.mount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,6 +97,13 @@ class LiveEditor {
|
|||
return this.source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback called with the editor is mounted in DOM.
|
||||
*/
|
||||
onMount(callback) {
|
||||
this._onMount.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback called with a new cell content whenever it changes.
|
||||
*/
|
||||
|
@ -97,16 +126,22 @@ class LiveEditor {
|
|||
}
|
||||
|
||||
focus() {
|
||||
this._ensureMounted();
|
||||
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
blur() {
|
||||
this._ensureMounted();
|
||||
|
||||
if (this.editor.hasTextFocus()) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
insert(text) {
|
||||
this._ensureMounted();
|
||||
|
||||
const range = this.editor.getSelection();
|
||||
this.editor
|
||||
.getModel()
|
||||
|
@ -117,13 +152,15 @@ class LiveEditor {
|
|||
* Performs necessary cleanup actions.
|
||||
*/
|
||||
dispose() {
|
||||
// Explicitly destroy the editor instance and its text model.
|
||||
this.editor.dispose();
|
||||
if (this.isMounted()) {
|
||||
// Explicitly destroy the editor instance and its text model.
|
||||
this.editor.dispose();
|
||||
|
||||
const model = this.editor.getModel();
|
||||
const model = this.editor.getModel();
|
||||
|
||||
if (model) {
|
||||
model.dispose();
|
||||
if (model) {
|
||||
model.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,6 +168,8 @@ class LiveEditor {
|
|||
* Either adds or moves remote user cursor to the new position.
|
||||
*/
|
||||
updateUserSelection(client, selection) {
|
||||
this._ensureMounted();
|
||||
|
||||
if (this._remoteUserByClientPid[client.pid]) {
|
||||
this._remoteUserByClientPid[client.pid].update(selection);
|
||||
} else {
|
||||
|
@ -147,6 +186,8 @@ class LiveEditor {
|
|||
* Removes remote user cursor.
|
||||
*/
|
||||
removeUserSelection(client) {
|
||||
this._ensureMounted();
|
||||
|
||||
if (this._remoteUserByClientPid[client.pid]) {
|
||||
this._remoteUserByClientPid[client.pid].dispose();
|
||||
delete this._remoteUserByClientPid[client.pid];
|
||||
|
@ -159,6 +200,8 @@ class LiveEditor {
|
|||
* To clear an existing marker `null` error is also supported.
|
||||
*/
|
||||
setCodeErrorMarker(error) {
|
||||
this._ensureMounted();
|
||||
|
||||
const owner = "livebook.error.syntax";
|
||||
|
||||
if (error) {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* which is responsible for controlling client-server communication
|
||||
* and synchronizing the sent/received changes.
|
||||
*
|
||||
* This class takes `serverAdapter` and `editorAdapter` objects
|
||||
* This class uses `serverAdapter` and `editorAdapter` objects
|
||||
* that encapsulate the logic relevant for each part.
|
||||
*
|
||||
* ## Changes synchronization
|
||||
|
@ -21,19 +21,12 @@
|
|||
* deltas and applied to the editor.
|
||||
*/
|
||||
export default class EditorClient {
|
||||
constructor(serverAdapter, editorAdapter, revision) {
|
||||
constructor(serverAdapter, revision) {
|
||||
this.serverAdapter = serverAdapter;
|
||||
this.editorAdapter = editorAdapter;
|
||||
this.revision = revision;
|
||||
this.state = new Synchronized(this);
|
||||
this._onDelta = null;
|
||||
|
||||
this.editorAdapter.onDelta((delta) => {
|
||||
this._handleClientDelta(delta);
|
||||
// This delta comes from the editor, so it has already been applied.
|
||||
this._emitDelta(delta);
|
||||
});
|
||||
|
||||
this.serverAdapter.onDelta((delta) => {
|
||||
this._handleServerDelta(delta);
|
||||
});
|
||||
|
@ -43,6 +36,22 @@ export default class EditorClient {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugs in the editor adapter.
|
||||
*
|
||||
* The adapter may be set at a later point after initialization, in
|
||||
* case the editor is mounted lazily.
|
||||
*/
|
||||
setEditorAdapter(editorAdapter) {
|
||||
this.editorAdapter = editorAdapter;
|
||||
|
||||
this.editorAdapter.onDelta((delta) => {
|
||||
this._handleClientDelta(delta);
|
||||
// This delta comes from the editor, so it has already been applied.
|
||||
this._emitDelta(delta);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback called with a every delta applied to the editor.
|
||||
*
|
||||
|
@ -72,7 +81,7 @@ export default class EditorClient {
|
|||
}
|
||||
|
||||
applyDelta(delta) {
|
||||
this.editorAdapter.applyDelta(delta);
|
||||
this.editorAdapter && this.editorAdapter.applyDelta(delta);
|
||||
// This delta comes from the server and we have just applied it to the editor.
|
||||
this._emitDelta(delta);
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ defmodule LivebookWeb.SessionLive.CellEditorComponent do
|
|||
data-cell-id={@cell_id}
|
||||
data-tag={@tag}>
|
||||
<div class="py-3 rounded-lg bg-editor" data-el-editor-container>
|
||||
<div class="px-8">
|
||||
<div class="px-8" data-el-skeleton>
|
||||
<.content_skeleton bg_class="bg-gray-500" empty={empty?(@source_view)} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue