Skip to content

Commit

Permalink
Notebook webview output optimization (#14234)
Browse files Browse the repository at this point in the history
* basic single backlayer webview

Signed-off-by: Jonah Iden <[email protected]>

* allow interacting with outputs

Signed-off-by: Jonah Iden <[email protected]>

* output presentation change and more smaller improvements

Signed-off-by: Jonah Iden <[email protected]>

* output height fixes

Signed-off-by: Jonah Iden <[email protected]>

* output height calculation

Signed-off-by: Jonah Iden <[email protected]>

* collapsing outputs

Signed-off-by: Jonah Iden <[email protected]>

* fixed ouptut presentation change

Signed-off-by: Jonah Iden <[email protected]>

* removed testing console logs

Signed-off-by: Jonah Iden <[email protected]>

* fixed interacting with cells regardless of position focusing it

Signed-off-by: Jonah Iden <[email protected]>

* fixed some errors when closing a notebook

Signed-off-by: Jonah Iden <[email protected]>

* improved renderer failing

Signed-off-by: Jonah Iden <[email protected]>

* fixed issue with cell height changes

Signed-off-by: Jonah Iden <[email protected]>

* fixed outputs when reopening  notebook editors

Signed-off-by: Jonah Iden <[email protected]>

* fix iframe height

Signed-off-by: Jonah Iden <[email protected]>

---------

Signed-off-by: Jonah Iden <[email protected]>
  • Loading branch information
jonah-iden authored Oct 8, 2024
1 parent 8f0f4b1 commit d999fe0
Show file tree
Hide file tree
Showing 16 changed files with 735 additions and 295 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { NotebookCommands } from './notebook-actions-contribution';
import { changeCellType } from './cell-operations';
import { EditorLanguageQuickPickService } from '@theia/editor/lib/browser/editor-language-quick-pick-service';
import { NotebookService } from '../service/notebook-service';
import { NOTEBOOK_EDITOR_ID_PREFIX } from '../notebook-editor-widget';

export namespace NotebookCellCommands {
/** Parameters: notebookModel: NotebookModel | undefined, cell: NotebookCellModel */
Expand Down Expand Up @@ -371,7 +372,9 @@ export class NotebookCellActionContribution implements MenuContribution, Command
}], true)
));
commands.registerCommand(NotebookCellCommands.CHANGE_OUTPUT_PRESENTATION_COMMAND, this.editableCellCommandHandler(
(_, __, output) => output?.requestOutputPresentationUpdate()
(notebook, cell, output) => {
this.notebookEditorWidgetService.getNotebookEditor(NOTEBOOK_EDITOR_ID_PREFIX + notebook.uri.toString())?.requestOuputPresentationChange(cell.handle, output);
}
));

const insertCommand = (type: CellKind, index: number | 'above' | 'below', focusContainer: boolean): CommandHandler => this.editableCellCommandHandler(() =>
Expand Down
39 changes: 32 additions & 7 deletions packages/notebook/src/browser/notebook-editor-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import * as React from '@theia/core/shared/react';
import { CommandRegistry, MenuModelRegistry, URI } from '@theia/core';
import { ReactWidget, Navigatable, SaveableSource, Message, DelegatingSaveable, lock, unlock, animationFrame } from '@theia/core/lib/browser';
import { ReactNode } from '@theia/core/shared/react';
import { CellKind } from '../common';
import { CellKind, NotebookCellsChangeType } from '../common';
import { CellRenderer as CellRenderer, NotebookCellListView } from './view/notebook-cell-list-view';
import { NotebookCodeCellRenderer } from './view/notebook-code-cell-view';
import { NotebookMarkdownCellRenderer } from './view/notebook-markdown-cell-view';
Expand All @@ -35,6 +35,8 @@ import { NotebookViewportService } from './view/notebook-viewport-service';
import { NotebookCellCommands } from './contributions/notebook-cell-actions-contribution';
import { NotebookFindWidget } from './view/notebook-find-widget';
import debounce = require('lodash/debounce');
import { CellOutputWebview, CellOutputWebviewFactory } from './renderers/cell-output-webview';
import { NotebookCellOutputModel } from './view-model/notebook-cell-output-model';
const PerfectScrollbar = require('react-perfect-scrollbar');

export const NotebookEditorWidgetContainerFactory = Symbol('NotebookEditorWidgetContainerFactory');
Expand All @@ -44,6 +46,9 @@ export function createNotebookEditorWidgetContainer(parent: interfaces.Container

child.bind(NotebookEditorProps).toConstantValue(props);

const cellOutputWebviewFactory: CellOutputWebviewFactory = parent.get(CellOutputWebviewFactory);
child.bind(CellOutputWebview).toConstantValue(cellOutputWebviewFactory());

child.bind(NotebookContextManager).toSelf().inSingletonScope();
child.bind(NotebookMainToolbarRenderer).toSelf().inSingletonScope();
child.bind(NotebookCellToolbarFactory).toSelf().inSingletonScope();
Expand Down Expand Up @@ -104,6 +109,9 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
@inject(NotebookViewportService)
protected readonly viewportService: NotebookViewportService;

@inject(CellOutputWebview)
protected readonly cellOutputWebview: CellOutputWebview;

protected readonly onDidChangeModelEmitter = new Emitter<void>();
readonly onDidChangeModel = this.onDidChangeModelEmitter.event;

Expand Down Expand Up @@ -173,11 +181,18 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
this.commandRegistry.executeCommand(NotebookCellCommands.EDIT_COMMAND.id, model, model.cells[0]);
model.setSelectedCell(model.cells[0]);
}
model.onDidChangeContent(changeEvents => {
const cellEvent = changeEvents.filter(event => event.kind === NotebookCellsChangeType.Move || event.kind === NotebookCellsChangeType.ModelChange);
if (cellEvent.length > 0) {
this.cellOutputWebview.cellsChanged(cellEvent);
}
});
});
}

protected async waitForData(): Promise<NotebookModel> {
this._model = await this.props.notebookData;
this.cellOutputWebview.init(this._model, this);
this.saveable.delegate = this._model;
this.toDispose.push(this._model);
this.toDispose.push(this._model.onDidChangeContent(() => {
Expand Down Expand Up @@ -261,12 +276,15 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
<PerfectScrollbar className='theia-notebook-scroll-container'
ref={this.scrollBarRef}
onScrollY={(e: HTMLDivElement) => this.viewportService.onScroll(e)}>
<NotebookCellListView renderers={this.renderers}
notebookModel={this._model}
notebookContext={this.notebookContextManager}
toolbarRenderer={this.cellToolbarFactory}
commandRegistry={this.commandRegistry}
menuRegistry={this.menuRegistry} />
<div className='theia-notebook-scroll-area'>
{this.cellOutputWebview.render()}
<NotebookCellListView renderers={this.renderers}
notebookModel={this._model}
notebookContext={this.notebookContextManager}
toolbarRenderer={this.cellToolbarFactory}
commandRegistry={this.commandRegistry}
menuRegistry={this.menuRegistry} />
</div>
</PerfectScrollbar>
</div>
</div>;
Expand All @@ -282,6 +300,12 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
this.notebookEditorService.removeNotebookEditor(this);
}

requestOuputPresentationChange(cellHandle: number, output?: NotebookCellOutputModel): void {
if (output) {
this.cellOutputWebview.requestOutputPresentationUpdate(cellHandle, output);
}
}

postKernelMessage(message: unknown): void {
this.onDidPostKernelMessageEmitter.fire(message);
}
Expand All @@ -307,6 +331,7 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
}

override dispose(): void {
this.cellOutputWebview.dispose();
this.notebookContextManager.dispose();
this.onDidChangeModelEmitter.dispose();
this.onDidPostKernelMessageEmitter.dispose();
Expand Down
24 changes: 21 additions & 3 deletions packages/notebook/src/browser/renderers/cell-output-webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,38 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { Disposable } from '@theia/core';
import { NotebookCellModel } from '../view-model/notebook-cell-model';
import { Disposable, Event } from '@theia/core';
import { NotebookModel } from '../view-model/notebook-model';
import { NotebookEditorWidget } from '../notebook-editor-widget';
import { NotebookContentChangedEvent } from '../notebook-types';
import { NotebookCellOutputModel } from '../view-model/notebook-cell-output-model';
import { NotebookCellModel } from '../view-model/notebook-cell-model';

export const CellOutputWebviewFactory = Symbol('outputWebviewFactory');
export const CellOutputWebview = Symbol('outputWebview');

export type CellOutputWebviewFactory = (cell: NotebookCellModel, notebook: NotebookModel) => Promise<CellOutputWebview>;
export type CellOutputWebviewFactory = () => Promise<CellOutputWebview>;

export interface OutputRenderEvent {
cellHandle: number;
outputId: string;
outputHeight: number;
}

export interface CellOutputWebview extends Disposable {

readonly id: string;

init(notebook: NotebookModel, editor: NotebookEditorWidget): void;

render(): React.ReactNode;

setCellHeight(cell: NotebookCellModel, height: number): void;
cellsChanged(cellEvent: NotebookContentChangedEvent[]): void;
onDidRenderOutput: Event<OutputRenderEvent>

requestOutputPresentationUpdate(cellHandle: number, output: NotebookCellOutputModel): void;

attachWebview(): void;
isAttached(): boolean
}
44 changes: 35 additions & 9 deletions packages/notebook/src/browser/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,24 @@
}

.theia-notebook-cell-list {
position: absolute;
top: 0;
width: 100%;
overflow-y: auto;
list-style: none;
padding-left: 0px;
background-color: var(--theia-notebook-editorBackground);
z-index: 0;
pointer-events: none;
}


.theia-notebook-cell-output-webview {
padding: 5px 0px;
margin: 0px 15px 0px 50px;
width: calc(100% - 60px);
position: absolute;
z-index: 0;
}

.theia-notebook-cell {
Expand Down Expand Up @@ -69,7 +83,8 @@
/* Rendered Markdown Content */

.theia-notebook-markdown-content {
padding: 8px 16px 8px 36px;
pointer-events: all;
padding: 8px 16px 8px 0px;
font-size: var(--theia-notebook-markdown-size);
}

Expand All @@ -87,9 +102,13 @@
padding-bottom: 0;
}

.theia-notebook-markdown-sidebar {
width: 35px;
}

/* Markdown cell edit mode */
.theia-notebook-cell-content:has(.theia-notebook-markdown-editor-container>.theia-notebook-cell-editor) {
margin-left: 36px;
pointer-events: all;
margin-right: var(--theia-notebook-cell-editor-margin-right);
outline: 1px solid var(--theia-notebook-cellBorderColor);
}
Expand All @@ -108,6 +127,7 @@
}

.theia-notebook-cell-editor-container {
pointer-events: all;
width: calc(100% - 46px);
flex: 1;
outline: 1px solid var(--theia-notebook-cellBorderColor);
Expand Down Expand Up @@ -149,6 +169,7 @@
}

.theia-notebook-cell-toolbar {
pointer-events: all;
border: 1px solid var(--theia-notebook-cellToolbarSeparator);
display: flex;
position: absolute;
Expand All @@ -161,11 +182,15 @@
display: flex;
flex-direction: column;
padding: 2px;
background-color: var(--theia-editor-background);
flex-grow: 1;
}

.theia-notebook-cell-sidebar {
pointer-events: all;
display: flex;
}

.theia-notebook-cell-sidebar-actions {
display: flex;
flex-direction: column;
}
Expand Down Expand Up @@ -194,6 +219,7 @@
}

.theia-notebook-cell-divider {
pointer-events: all;
height: 25px;
width: 100%;
}
Expand Down Expand Up @@ -303,19 +329,19 @@
margin: 1px 0 0 4px;
}

.theia-notebook-cell-output-webview {
padding: 5px 0px;
margin: 0px 15px 0px 9px;
width: 100%;
}

.theia-notebook-cell-drop-indicator {
height: 2px;
background-color: var(--theia-notebook-focusedCellBorder);
width: 100%;
}

.theia-notebook-collapsed-output-container {
width: 0;
overflow: visible;
}

.theia-notebook-collapsed-output {
text-wrap: nowrap;
padding: 4px 8px;
color: var(--theia-foreground);
margin-left: 30px;
Expand Down
13 changes: 13 additions & 0 deletions packages/notebook/src/browser/view-model/notebook-cell-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ export class NotebookCellModel implements NotebookCell, Disposable {
protected onDidRequestCenterEditorEmitter = new Emitter<void>();
readonly onDidRequestCenterEditor = this.onDidRequestCenterEditorEmitter.event;

protected onDidCellHeightChangeEmitter = new Emitter<number>();
readonly onDidCellHeightChange = this.onDidCellHeightChangeEmitter.event;

@inject(NotebookCellModelProps)
protected readonly props: NotebookCellModelProps;

Expand Down Expand Up @@ -251,6 +254,16 @@ export class NotebookCellModel implements NotebookCell, Disposable {
}
}

protected _cellheight: number = 0;
get cellHeight(): number {
return this._cellheight;
}

set cellHeight(height: number) {
this.onDidCellHeightChangeEmitter.fire(height);
this._cellheight = height;
}

@postConstruct()
protected init(): void {
this._outputs = this.props.outputs.map(op => new NotebookCellOutputModel(op));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ export class NotebookCellOutputModel implements Disposable {
private didChangeDataEmitter = new Emitter<void>();
readonly onDidChangeData = this.didChangeDataEmitter.event;

private requestOutputPresentationChangeEmitter = new Emitter<void>();
readonly onRequestOutputPresentationChange = this.requestOutputPresentationChangeEmitter.event;

get outputId(): string {
return this.rawOutput.outputId;
}
Expand Down Expand Up @@ -54,11 +51,6 @@ export class NotebookCellOutputModel implements Disposable {

dispose(): void {
this.didChangeDataEmitter.dispose();
this.requestOutputPresentationChangeEmitter.dispose();
}

requestOutputPresentationUpdate(): void {
this.requestOutputPresentationChangeEmitter.fire();
}

getData(): CellOutput {
Expand Down
6 changes: 5 additions & 1 deletion packages/notebook/src/browser/view-model/notebook-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,10 +509,14 @@ export class NotebookModel implements Saveable, Disposable {
return true;
}

protected getCellIndexByHandle(handle: number): number {
getCellIndexByHandle(handle: number): number {
return this.cells.findIndex(c => c.handle === handle);
}

getCellByHandle(handle: number): NotebookCellModel | undefined {
return this.cells.find(c => c.handle === handle);
}

protected isCellMetadataChanged(a: NotebookCellMetadata, b: NotebookCellMetadata): boolean {
const keys = new Set<keyof NotebookCellMetadata>([...Object.keys(a || {}), ...Object.keys(b || {})]);
for (const key of keys) {
Expand Down
Loading

0 comments on commit d999fe0

Please sign in to comment.