diff --git a/packages/libro-code-cell/src/code-cell-view.tsx b/packages/libro-code-cell/src/code-cell-view.tsx index fa377a38..50850984 100644 --- a/packages/libro-code-cell/src/code-cell-view.tsx +++ b/packages/libro-code-cell/src/code-cell-view.tsx @@ -210,7 +210,7 @@ export class LibroCodeCellView extends LibroEditableExecutableCellView { cell: this, toJSON: () => ({ cellId: this.id }), }) - .then(async (outputArea) => { + .then(async (outputArea: LibroOutputArea) => { this.outputArea = outputArea; const output = this.outputs; if (isOutput(output)) { diff --git a/packages/libro-core/src/cell/libro-cell-view.tsx b/packages/libro-core/src/cell/libro-cell-view.tsx index c08b30e7..e105497a 100644 --- a/packages/libro-core/src/cell/libro-cell-view.tsx +++ b/packages/libro-core/src/cell/libro-cell-view.tsx @@ -168,10 +168,12 @@ export class LibroCellView extends BaseView implements CellView { } disposed = false; + override dispose() { this.toDispose.dispose(); super.dispose(); } + toJSON(): LibroCell { const meta = { ...(this.model.toJSON() as LibroCell) }; const modelContribution = this.cellService.findModelProvider(this.model.options); diff --git a/packages/libro-core/src/libro-view.tsx b/packages/libro-core/src/libro-view.tsx index 42535b3e..16512428 100644 --- a/packages/libro-core/src/libro-view.tsx +++ b/packages/libro-core/src/libro-view.tsx @@ -666,6 +666,9 @@ export class LibroView extends BaseView implements NotebookView { }; deleteCell = (cell: CellView) => { + if (this.disposed) { + return; + } if (this.libroViewTracker.isEnabledSpmReporter && cell.model.id) { const id = cell.model.id + this.id; const libroTracker = this.libroViewTracker.getOrCreateTrackers({ @@ -1375,10 +1378,15 @@ export class LibroView extends BaseView implements NotebookView { override dispose() { if (!this.disposed) { + this.disposed = true; this.libroService.deleteLibroViewFromCache(this); this.toDispose.dispose(); + this.model.cells.forEach((cell) => { + if (!cell.disposed) { + cell.dispose(); + } + }); } - this.disposed = true; super.dispose(); } diff --git a/packages/libro-core/src/output/output-model.tsx b/packages/libro-core/src/output/output-model.tsx index ee74fcc6..780f6f20 100644 --- a/packages/libro-core/src/output/output-model.tsx +++ b/packages/libro-core/src/output/output-model.tsx @@ -1,6 +1,14 @@ import type { IOutput, JSONObject, PartialJSONObject } from '@difizen/libro-common'; -import { BaseView, Emitter, prop, view, ViewOption } from '@difizen/libro-common/app'; -import { inject, transient } from '@difizen/libro-common/app'; +import { + BaseView, + Emitter, + prop, + view, + ViewOption, + DisposableCollection, + inject, + transient, +} from '@difizen/libro-common/app'; import type { FC } from 'react'; import { v4 } from 'uuid'; @@ -33,6 +41,8 @@ export class LibroOutputView extends BaseView implements BaseOutputView { @prop() data: JSONObject; + protected override toDispose = new DisposableCollection(); + onUpdateEmitter = new Emitter(); get onUpdate() { @@ -51,14 +61,18 @@ export class LibroOutputView extends BaseView implements BaseOutputView { this.type = 'libro-default-output'; this.data = {}; this.metadata = {}; - this.onUpdate(() => { - this.cell.parent.model.onChange?.(); - }); + this.toDispose.push(this.onUpdateEmitter); + this.toDispose.push( + this.onUpdate(() => { + this.cell.parent.model.onChange?.(); + }), + ); } render: FC<{ output: BaseOutputView }> = LibroOutputModelRender; override dispose() { + this.toDispose.dispose(); super.dispose(); } toJSON() { diff --git a/packages/libro-jupyter/src/output/libro-jupyter-outputarea.tsx b/packages/libro-jupyter/src/output/libro-jupyter-outputarea.tsx index ea536963..6522605a 100644 --- a/packages/libro-jupyter/src/output/libro-jupyter-outputarea.tsx +++ b/packages/libro-jupyter/src/output/libro-jupyter-outputarea.tsx @@ -1,4 +1,11 @@ import type * as nbformat from '@difizen/libro-common'; +import { + inject, + transient, + view, + ViewOption, + DisposableCollection, +} from '@difizen/libro-common/app'; import type { LibroEditableExecutableCellView, ExecutableCellModel, @@ -15,13 +22,13 @@ import { isStreamMsg, isUpdateDisplayDataMsg, } from '@difizen/libro-kernel'; -import { inject, transient, view, ViewOption } from '@difizen/libro-common/app'; @transient() @view('libro-output-area') export class LibroJupyterOutputArea extends LibroOutputArea { declare cell: LibroEditableExecutableCellView; protected displayIdMap = new Map(); + protected override toDispose = new DisposableCollection(); constructor(@inject(ViewOption) option: IOutputAreaOption) { super(option); @@ -30,69 +37,72 @@ export class LibroJupyterOutputArea extends LibroOutputArea { handleMsg() { const cellModel = this.cell.model as ExecutableCellModel; - cellModel.msgChangeEmitter.event((msg) => { - const transientMsg = (msg.content.transient || {}) as nbformat.JSONObject; - const displayId = transientMsg['display_id'] as string; - if (isExecuteInputMsg(msg)) { - cellModel.executeCount = msg.content.execution_count; - } - if ( - isDisplayDataMsg(msg) || - isStreamMsg(msg) || - isErrorMsg(msg) || - isExecuteResultMsg(msg) - ) { - const output: nbformat.IOutput = { - ...msg.content, - output_type: msg.header.msg_type, - }; - this.add(output); - } - if (isUpdateDisplayDataMsg(msg)) { - const output = { ...msg.content, output_type: 'display_data' }; - const targets = this.displayIdMap.get(displayId); - if (targets) { - for (const index of targets) { - this.set(index, output); - } + this.toDispose.push( + cellModel.msgChangeEmitter.event((msg: any) => { + const transientMsg = (msg.content.transient || {}) as nbformat.JSONObject; + const displayId = transientMsg['display_id'] as string; + if (isExecuteInputMsg(msg)) { + cellModel.executeCount = msg.content.execution_count; } - } - if (displayId && isDisplayDataMsg(msg)) { - const targets = this.displayIdMap.get(displayId) || []; - targets.push(this.outputs.length); - this.displayIdMap.set(displayId, targets); - } - //Handle an execute reply message. - if (isExecuteReplyMsg(msg)) { - const content = msg.content; - if (content.status !== 'ok') { - return; + if ( + isDisplayDataMsg(msg) || + isStreamMsg(msg) || + isErrorMsg(msg) || + isExecuteResultMsg(msg) + ) { + const output: nbformat.IOutput = { + ...msg.content, + output_type: msg.header.msg_type, + }; + this.add(output); + } + if (isUpdateDisplayDataMsg(msg)) { + const output = { ...msg.content, output_type: 'display_data' }; + const targets = this.displayIdMap.get(displayId); + if (targets) { + for (const index of targets) { + this.set(index, output); + } + } } - const payload = content && content.payload; - if (!payload || !payload.length) { - return; + if (displayId && isDisplayDataMsg(msg)) { + const targets = this.displayIdMap.get(displayId) || []; + targets.push(this.outputs.length); + this.displayIdMap.set(displayId, targets); } - const pages = payload.filter((i: any) => i.source === 'page'); - if (!pages.length) { - return; + //Handle an execute reply message. + if (isExecuteReplyMsg(msg)) { + const content = msg.content; + if (content.status !== 'ok') { + return; + } + const payload = content && content.payload; + if (!payload || !payload.length) { + return; + } + const pages = payload.filter((i: any) => i.source === 'page'); + if (!pages.length) { + return; + } + const page = JSON.parse(JSON.stringify(pages[0])); + const output: nbformat.IOutput = { + output_type: 'display_data', + data: page.data as nbformat.IMimeBundle, + metadata: {}, + }; + this.add(output); } - const page = JSON.parse(JSON.stringify(pages[0])); - const output: nbformat.IOutput = { - output_type: 'display_data', - data: page.data as nbformat.IMimeBundle, - metadata: {}, - }; - this.add(output); - } - if (isClearOutputMsg(msg)) { - const wait = msg.content.wait; - this.clear(wait); - } - }); + if (isClearOutputMsg(msg)) { + const wait = msg.content.wait; + this.clear(wait); + } + }), + ); } override dispose(): void { + this.toDispose.dispose(); this.displayIdMap.clear(); super.dispose(); } diff --git a/packages/libro-prompt-cell/src/prompt-cell-view.tsx b/packages/libro-prompt-cell/src/prompt-cell-view.tsx index 5e211712..cf648bac 100644 --- a/packages/libro-prompt-cell/src/prompt-cell-view.tsx +++ b/packages/libro-prompt-cell/src/prompt-cell-view.tsx @@ -438,7 +438,7 @@ export class LibroPromptCellView extends LibroEditableExecutableCellView { cell: this, toJSON: () => ({ cellId: this.id }), }) - .then(async (outputArea) => { + .then(async (outputArea: LibroOutputArea) => { this.outputArea = outputArea; const output = this.outputs; if (isOutput(output)) { diff --git a/packages/libro-sql-cell/src/libro-sql-cell-view.tsx b/packages/libro-sql-cell/src/libro-sql-cell-view.tsx index 37da1d5c..cd0da965 100644 --- a/packages/libro-sql-cell/src/libro-sql-cell-view.tsx +++ b/packages/libro-sql-cell/src/libro-sql-cell-view.tsx @@ -344,7 +344,7 @@ export class LibroSqlCellView extends LibroEditableExecutableCellView { cell: this, toJSON: () => ({ cellId: this.id }), }) - .then(async (outputArea) => { + .then(async (outputArea: LibroOutputArea) => { this.outputArea = outputArea; const output = this.outputs; if (isOutput(output)) {