From c8420caa5f0766bcdec6de27989d81e19c1c9b36 Mon Sep 17 00:00:00 2001 From: Marcos Alves Date: Fri, 28 Jun 2024 11:15:20 -0300 Subject: [PATCH] feat: adding more functionalities to markdown Cell (#247) --- packages/react/src/components/cell/Cell.tsx | 51 ++++++++++++++----- .../react/src/components/cell/CellAdapter.ts | 45 ++++++++-------- .../react/src/components/cell/CellCommands.ts | 12 +++-- packages/react/src/examples/Notebook.tsx | 9 +++- 4 files changed, 76 insertions(+), 41 deletions(-) diff --git a/packages/react/src/components/cell/Cell.tsx b/packages/react/src/components/cell/Cell.tsx index 5ea93d6e..099c32c3 100644 --- a/packages/react/src/components/cell/Cell.tsx +++ b/packages/react/src/components/cell/Cell.tsx @@ -5,7 +5,7 @@ */ import { useState, useEffect } from 'react'; -import { CodeCell } from '@jupyterlab/cells'; +import { CodeCell, MarkdownCell } from '@jupyterlab/cells'; import { KernelMessage } from '@jupyterlab/services'; import { Box } from '@primer/react'; import CellAdapter from './CellAdapter'; @@ -35,22 +35,29 @@ export const Cell = (props: ICellProps) => { const cellStore = useCellStore(); const [adapter, setAdapter] = useState(); - const handleCodeCellState = (adapter: CellAdapter) => { - (adapter.codeCell as CodeCell).outputArea.outputLengthChanged?.connect( - (outputArea, outputsCount) => { - cellStore.setOutputsCount(outputsCount); - } - ); + const handleCellInitEvents = (adapter: CellAdapter) => { + if (adapter.cell instanceof CodeCell) { + adapter.cell.outputArea.outputLengthChanged?.connect( + (outputArea, outputsCount) => { + cellStore.setOutputsCount(outputsCount); + } + ); + } + adapter.sessionContext.initialize().then(() => { - if (autoStart) { + if (autoStart && adapter.cell instanceof CodeCell) { const execute = CodeCell.execute( - (adapter.codeCell as CodeCell), + adapter.cell, adapter.sessionContext ); execute.then((msg: void | KernelMessage.IExecuteReplyMsg) => { cellStore.setKernelAvailable(true); }); } + + if (autoStart && adapter.cell instanceof MarkdownCell) { + adapter.cell.rendered = true; + } }); } @@ -65,16 +72,34 @@ export const Cell = (props: ICellProps) => { }); cellStore.setAdapter(adapter); cellStore.setSource(source); - adapter.codeCell.model.contentChanged.connect( + adapter.cell.model.contentChanged.connect( (cellModel, changedArgs) => { cellStore.setSource(cellModel.sharedModel.getSource()); } ); - if (type === 'code') { - handleCodeCellState(adapter); - } + handleCellInitEvents(adapter); setAdapter(adapter); + const handleDblClick = (event: Event) => { + let target = event.target as HTMLElement; + + /** + * Find the DOM searching by the markdown output class (since child elements can be clicked also) + * If a rendered markdown was found, then back cell to editor mode + */ + while (target && !target.classList.contains('jp-MarkdownOutput')) { + target = target.parentElement as HTMLElement; + } + if (target && target.classList.contains('jp-MarkdownOutput')) { + (adapter.cell as MarkdownCell).rendered = false; + } + }; + + // Adds the event for double click and the removal on component's destroy + document.addEventListener('dblclick', handleDblClick); + return () => { + document.removeEventListener('dblclick', handleDblClick); + }; }); } }, [source, defaultKernel, serverSettings]); diff --git a/packages/react/src/components/cell/CellAdapter.ts b/packages/react/src/components/cell/CellAdapter.ts index f402ec52..5fce9ef1 100755 --- a/packages/react/src/components/cell/CellAdapter.ts +++ b/packages/react/src/components/cell/CellAdapter.ts @@ -54,7 +54,7 @@ import getMarked from '../notebook/marked/marked'; import CellCommands from './CellCommands'; export class CellAdapter { - private _codeCell: CodeCell | MarkdownCell | RawCell; + private _cell: CodeCell | MarkdownCell | RawCell; private _kernel: Kernel; private _panel: BoxPanel; private _sessionContext: SessionContext; @@ -211,7 +211,7 @@ export class CellAdapter { }); if (type === 'code') { - this._codeCell = new CodeCell({ + this._cell = new CodeCell({ rendermime, model: new CodeCellModel({ sharedModel: createStandaloneCell({ @@ -225,7 +225,7 @@ export class CellAdapter { }), }); } else if (type === 'markdown') { - this._codeCell = new MarkdownCell({ + this._cell = new MarkdownCell({ rendermime, model: new MarkdownCellModel({ sharedModel: createStandaloneCell({ @@ -240,11 +240,11 @@ export class CellAdapter { }); } - this._codeCell.addClass('dla-Jupyter-Cell'); - this._codeCell.initializeState(); + this._cell.addClass('dla-Jupyter-Cell'); + this._cell.initializeState(); if (this._type === 'markdown') { - (this._codeCell as MarkdownCell).rendered = false; + (this._cell as MarkdownCell).rendered = false; } this._sessionContext.kernelChanged.connect( @@ -270,18 +270,18 @@ export class CellAdapter { if (this._type === 'code') { const lang = info.language_info; const mimeType = mimeService.getMimeTypeByLanguage(lang); - this._codeCell.model.mimeType = mimeType; + this._cell.model.mimeType = mimeType; } }); }); - const editor = this._codeCell.editor; + const editor = this._cell.editor; const model = new CompleterModel(); const completer = new Completer({ editor, model }); const timeout = 1000; const provider = new KernelCompleterProvider(); const reconciliator = new ProviderReconciliator({ context: { - widget: this._codeCell, + widget: this._cell, editor, session: this._sessionContext.session, }, @@ -293,7 +293,7 @@ export class CellAdapter { const provider = new KernelCompleterProvider(); handler.reconciliator = new ProviderReconciliator({ context: { - widget: this._codeCell, + widget: this._cell, editor, session: this._sessionContext.session, }, @@ -303,9 +303,7 @@ export class CellAdapter { }); handler.editor = editor; - if (this._type === 'code') { - CellCommands(commands, (this._codeCell as CodeCell)!, this._sessionContext, handler); - } + CellCommands(commands, this._cell!, this._sessionContext, handler); completer.hide(); completer.addClass('jp-Completer-Cell'); Widget.attach(completer, document.body); @@ -315,9 +313,9 @@ export class CellAdapter { icon: runIcon, onClick: () => { if (this._type === 'code') { - CodeCell.execute(this._codeCell as CodeCell, this._sessionContext); + CodeCell.execute(this._cell as CodeCell, this._sessionContext); } else if (this._type === 'markdown') { - (this.codeCell as MarkdownCell).rendered = true; + (this._cell as MarkdownCell).rendered = true; } }, tooltip: 'Run', @@ -338,17 +336,18 @@ export class CellAdapter { ); if (this._type === 'code') { - (this._codeCell as CodeCell).outputsScrolled = false; + (this._cell as CodeCell).outputsScrolled = false; } - this._codeCell.activate(); + this._cell.activate(); this._panel = new BoxPanel(); this._panel.direction = 'top-to-bottom'; this._panel.spacing = 0; this._panel.addWidget(toolbar); - this._panel.addWidget(this._codeCell); + this._panel.addWidget(this._cell); + BoxPanel.setStretch(toolbar, 0); - BoxPanel.setStretch(this._codeCell, 1); + BoxPanel.setStretch(this._cell, 1); window.addEventListener('resize', () => { this._panel.update(); }); @@ -359,8 +358,8 @@ export class CellAdapter { return this._panel; } - get codeCell(): CodeCell | MarkdownCell | RawCell { - return this._codeCell; + get cell(): CodeCell | MarkdownCell | RawCell { + return this._cell; } get sessionContext(): SessionContext { @@ -373,9 +372,9 @@ export class CellAdapter { execute = () => { if (this._type === 'code') { - CodeCell.execute((this._codeCell as CodeCell), this._sessionContext); + CodeCell.execute((this._cell as CodeCell), this._sessionContext); } else if (this._type === 'markdown') { - (this._codeCell as MarkdownCell).rendered = true; + (this._cell as MarkdownCell).rendered = true; } }; } diff --git a/packages/react/src/components/cell/CellCommands.ts b/packages/react/src/components/cell/CellCommands.ts index bc93f421..b08f8b2e 100644 --- a/packages/react/src/components/cell/CellCommands.ts +++ b/packages/react/src/components/cell/CellCommands.ts @@ -6,7 +6,7 @@ import { CommandRegistry } from '@lumino/commands'; import { CompletionHandler } from '@jupyterlab/completer'; -import { CodeCell } from '@jupyterlab/cells'; +import { CodeCell, MarkdownCell, RawCell } from '@jupyterlab/cells'; import { SessionContext } from '@jupyterlab/apputils'; const cmdIds = { @@ -16,7 +16,7 @@ const cmdIds = { export const CellCommands = ( commandRegistry: CommandRegistry, - codeCell: CodeCell, + cell: CodeCell | MarkdownCell | RawCell, sessionContext: SessionContext, completerHandler: CompletionHandler ): void => { @@ -29,7 +29,13 @@ export const CellCommands = ( execute: () => completerHandler.completer.selectActive(), }); commandRegistry.addCommand('run:cell', { - execute: () => CodeCell.execute(codeCell, sessionContext), + execute: () => { + if (cell instanceof CodeCell) { + CodeCell.execute(cell, sessionContext) + } else if (cell instanceof MarkdownCell) { + (cell as MarkdownCell).rendered = true; + } + }, }); commandRegistry.addKeyBinding({ selector: '.jp-InputArea-editor.jp-mod-completer-enabled', diff --git a/packages/react/src/examples/Notebook.tsx b/packages/react/src/examples/Notebook.tsx index 0425ee65..a9314b80 100644 --- a/packages/react/src/examples/Notebook.tsx +++ b/packages/react/src/examples/Notebook.tsx @@ -7,6 +7,7 @@ import { createRoot } from 'react-dom/client'; import Jupyter from '../jupyter/Jupyter'; import Notebook from '../components/notebook/Notebook'; +import Cell from '../components/cell/Cell'; import NotebookToolbar from './toolbars/NotebookToolbar'; import CellSidebar from '../components/notebook/cell/sidebar/CellSidebarButton'; @@ -18,13 +19,17 @@ const root = createRoot(div); root.render( - Hello Aevo + {/* + /> */} + + + );