Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🪴external rendermime management #632

Merged
merged 2 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/tasty-ducks-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'demo-react': minor
'demo-core': minor
'thebe-react': minor
'thebe-core': minor
'thebe': patch
---

Made changes to the `thebe-core` APIs to make rendermime registries external, the caller now has to manage how registries are used across the other session nd notebook object. Updates the demos, `thebe` and `thebe-react` to reflect this base change. `thebe-react` now has a new provider making it easy to add a rendermine registry in the component tree.
1 change: 1 addition & 0 deletions _build/templates/site/myst/book-theme
Submodule book-theme added at df9cbc
6 changes: 5 additions & 1 deletion apps/demo-core/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ThebeSession } from 'thebe-core';
import {
makeRenderMimeRegistry,
ThebeEvents,
ThebeEventType,
shortId,
Expand Down Expand Up @@ -216,14 +217,17 @@ class App {
await this.server?.connectToJupyterServer();
}

this.session = await this.server.startNewSession();
const rendermime = makeRenderMimeRegistry(this.server.config.mathjax);

this.session = await this.server.startNewSession(rendermime);

this.notebook = ThebeNotebook.fromCodeBlocks(
code[this.exampleType].map((source) => ({
id: shortId(),
source,
})),
this.server.config,
rendermime,
);

if (this.session == null) console.error('could not start session');
Expand Down
2 changes: 1 addition & 1 deletion apps/demo-core/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?config=TeX-MML-AM_CHTML"
></script>
<script src="thebe-lite.min.js" type="text/javascript"></script>
<script src="demo.js" type="text/javascript"></script>
<script src="thebe-demo.js" type="text/javascript"></script>
<link rel="stylesheet" href="demo.css" />
<link rel="stylesheet" href="thebe-core.css" />
<link
Expand Down
10 changes: 6 additions & 4 deletions apps/demo-react/src/NotebookPage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { ThebeSessionProvider, useThebeServer } from 'thebe-react';
import { ThebeSessionProvider, ThebeRenderMimeRegistryProvider, useThebeServer } from 'thebe-react';

export function NotebookPage({ name, children }: React.PropsWithChildren<{ name: string }>) {
const { ready } = useThebeServer();

if (!ready) return null;
return (
<ThebeSessionProvider start name={name}>
{children}
</ThebeSessionProvider>
<ThebeRenderMimeRegistryProvider>
<ThebeSessionProvider start name={name}>
{children}
</ThebeSessionProvider>
</ThebeRenderMimeRegistryProvider>
);
}
2 changes: 0 additions & 2 deletions packages/core/src/cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ class ThebeCell extends PassiveCellRenderer implements IThebeCell {
* @param session
*/
attachSession(session: ThebeSession) {
session.manager.addWidgetFactories(this.rendermime);
this.session = session;
this.events.triggerStatus({
status: CellStatusEvent.attached,
Expand All @@ -85,7 +84,6 @@ class ThebeCell extends PassiveCellRenderer implements IThebeCell {
*
*/
detachSession() {
this.session?.manager.removeWidgetFactories(this.rendermime);
this.session = undefined;
this.events.triggerStatus({
status: CellStatusEvent.detached,
Expand Down
30 changes: 8 additions & 22 deletions packages/core/src/manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { RenderMimeRegistry, standardRendererFactories } from '@jupyterlab/rendermime';
import type { IKernelConnection } from '@jupyterlab/services/lib/kernel/kernel';
import type { Widget } from '@lumino/widgets';

Expand All @@ -26,34 +25,21 @@ export class ThebeManager extends KernelWidgetManager {
id: string;
_loader: RequireJsLoader;

constructor(kernel: IKernelConnection, rendermime?: IRenderMimeRegistry) {
const rm =
rendermime ??
new RenderMimeRegistry({
initialFactories: standardRendererFactories,
});
constructor(kernel: IKernelConnection, rendermime: IRenderMimeRegistry) {
super(kernel, rendermime);

this.id = shortId();
/** ensure this registry always gets the widget renderer.
* This is essential for cases where widgets are rendered heirarchically
*/
rm.addFactory(
{
safe: false,
mimeTypes: [WIDGET_MIMETYPE],
createRenderer: (options) => new WidgetRenderer(options, this as any),
},
1,
);
this.addWidgetFactories();

super(kernel, rm);

this.id = shortId();
this._registerWidgets();
this._loader = new RequireJsLoader();
}

addWidgetFactories(rendermime: IRenderMimeRegistry) {
rendermime.addFactory(
addWidgetFactories() {
this.rendermime.addFactory(
{
safe: false,
mimeTypes: [WIDGET_MIMETYPE],
Expand All @@ -63,8 +49,8 @@ export class ThebeManager extends KernelWidgetManager {
);
}

removeWidgetFactories(rendermime: IRenderMimeRegistry) {
rendermime.removeMimeType(WIDGET_MIMETYPE);
removeWidgetFactories() {
this.rendermime.removeMimeType(WIDGET_MIMETYPE);
}

/**
Expand Down
11 changes: 4 additions & 7 deletions packages/core/src/notebook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type ThebeSession from './session';
import type { IThebeCell, IThebeCellExecuteReturn } from './types';
import { shortId } from './utils';
import type { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { getRenderMimeRegistry } from './rendermime';
import type { Config } from './config';
import { EventSubject, NotebookStatusEvent } from './events';
import { EventEmitter } from './emitter';
Expand All @@ -24,16 +23,16 @@ class ThebeNotebook {
session?: ThebeSession;
protected events: EventEmitter;

constructor(id: string, config: Config, rendermime?: IRenderMimeRegistry) {
constructor(id: string, config: Config, rendermime: IRenderMimeRegistry) {
this.id = id;
this.events = new EventEmitter(id, config, EventSubject.notebook, this);
this.cells = [];
this.metadata = {};
this.rendermime = rendermime ?? getRenderMimeRegistry(config.mathjax);
this.rendermime = rendermime;
console.debug('thebe:notebook constructor', this);
}

static fromCodeBlocks(blocks: CodeBlock[], config: Config, rendermime?: IRenderMimeRegistry) {
static fromCodeBlocks(blocks: CodeBlock[], config: Config, rendermime: IRenderMimeRegistry) {
const id = shortId();
const notebook = new ThebeNotebook(id, config, rendermime);
notebook.cells = blocks.map((c) => {
Expand All @@ -46,7 +45,7 @@ class ThebeNotebook {
return notebook;
}

static fromIpynb(ipynb: INotebookContent, config: Config, rendermime?: IRenderMimeRegistry) {
static fromIpynb(ipynb: INotebookContent, config: Config, rendermime: IRenderMimeRegistry) {
const notebook = new ThebeNotebook(shortId(), config, rendermime);

Object.assign(notebook.metadata, ipynb.metadata);
Expand Down Expand Up @@ -119,7 +118,6 @@ class ThebeNotebook {
// note all cells in a notebook share the rendermime registry
// we only need to add the widgets factory once
this.session = session;
session.manager.addWidgetFactories(this.rendermime);
this.cells?.forEach((cell) => (cell.session = session));
this.events.triggerStatus({
status: NotebookStatusEvent.attached,
Expand All @@ -128,7 +126,6 @@ class ThebeNotebook {
}

detachSession() {
this.session?.manager.removeWidgetFactories(this.rendermime);
this.cells?.map((cell) => (cell.session = undefined));
this.session = undefined;
this.events.triggerStatus({
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/passive.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type * as nbformat from '@jupyterlab/nbformat';
import { getRenderMimeRegistry } from './rendermime';
import { makeRenderMimeRegistry } from './rendermime';
import { OutputArea, OutputAreaModel } from '@jupyterlab/outputarea';
import type { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import type { IPassiveCell, MathjaxOptions } from './types';
Expand Down Expand Up @@ -49,7 +49,7 @@ class PassiveCellRenderer implements IPassiveCell {

constructor(id: string, rendermime?: IRenderMimeRegistry, mathjax?: MathjaxOptions) {
this.id = id;
this.rendermime = rendermime ?? getRenderMimeRegistry(mathjax ?? makeMathjaxOptions());
this.rendermime = rendermime ?? makeRenderMimeRegistry(mathjax ?? makeMathjaxOptions());
this.model = new OutputAreaModel({ trusted: true });
this.area = new OutputArea({
model: this.model,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/rendermime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function getRenderers(mathjax: MathjaxOptions) {
};
}

export function getRenderMimeRegistry(mathjax?: MathjaxOptions) {
export function makeRenderMimeRegistry(mathjax?: MathjaxOptions) {
const rendermime = new RenderMimeRegistry(getRenderers(mathjax ?? makeMathjaxOptions()));
rendermime.addFactory(jsonRendererFactory, 10);
return rendermime;
Expand Down
20 changes: 12 additions & 8 deletions packages/core/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,24 @@ import type {
ServerSettings,
SessionIModel,
} from './types';
import type { Config } from './config';
import type { ServiceManager } from '@jupyterlab/services';
import type { LiteServerConfig } from 'thebe-lite';
import type { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import type { StatusEvent } from './events';
import { RepoProvider } from './types';
import { makeGitHubUrl, makeGitLabUrl, makeGitUrl } from './url';
import { getExistingServer, makeStorageKey, saveServerInfo } from './sessions';
import type { ServiceManager } from '@jupyterlab/services';
import {
KernelManager,
KernelSpecAPI,
ServerConnection,
SessionManager,
} from '@jupyterlab/services';
import ThebeSession from './session';
import type { Config } from './config';
import { shortId } from './utils';
import type { StatusEvent } from './events';
import { ServerStatusEvent, EventSubject, ErrorStatusEvent } from './events';
import { EventEmitter } from './emitter';
import { LiteServerConfig } from 'thebe-lite';

async function responseToJson(res: Response) {
if (!res.ok) throw Error(`${res.status} - ${res.statusText}`);
Expand Down Expand Up @@ -79,7 +80,10 @@ class ThebeServer implements ServerRuntime, ServerRestAPI {
this._isDisposed = true;
}

async startNewSession(kernelOptions?: KernelOptions): Promise<ThebeSession | null> {
async startNewSession(
rendermime: IRenderMimeRegistry,
kernelOptions?: KernelOptions,
): Promise<ThebeSession | null> {
await this.ready;

if (!this.sessionManager?.isReady) {
Expand All @@ -96,7 +100,7 @@ class ThebeServer implements ServerRuntime, ServerRestAPI {
},
});

return new ThebeSession(this, connection);
return new ThebeSession(this, connection, rendermime);
}

async listRunningSessions(): Promise<SessionIModel[]> {
Expand All @@ -116,15 +120,15 @@ class ThebeServer implements ServerRuntime, ServerRestAPI {
return this.listRunningSessions();
}

async connectToExistingSession(model: SessionIModel) {
async connectToExistingSession(model: SessionIModel, rendermime: IRenderMimeRegistry) {
await this.ready;
if (!this.sessionManager?.isReady) {
throw Error('Requesting session from a server, with no SessionManager available');
}

const connection = this.sessionManager?.connectTo({ model });

return new ThebeSession(this, connection);
return new ThebeSession(this, connection, rendermime);
}

async clearSavedBinderSessions() {
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { EventSubject, SessionStatusEvent } from './events';
import { ThebeManager } from './manager';
import type ThebeServer from './server';
import { EventEmitter } from './emitter';
import type { IRenderMimeRegistry } from '@jupyterlab/rendermime';

class ThebeSession {
readonly server: ThebeServer;
Expand All @@ -11,13 +12,17 @@ class ThebeSession {
private connection: ISessionConnection;
private events: EventEmitter;

constructor(server: ThebeServer, connection: ISessionConnection) {
constructor(
server: ThebeServer,
connection: ISessionConnection,
rendermime: IRenderMimeRegistry,
) {
this.server = server;
this.connection = connection;
this.events = new EventEmitter(this.connection.id, server.config, EventSubject.session, this);

if (this.connection.kernel == null) throw Error('ThebeSession - kernel is null');
this.manager = new ThebeManager(this.connection.kernel);
this.manager = new ThebeManager(this.connection.kernel, rendermime);

this.events.triggerStatus({
status: SessionStatusEvent.ready,
Expand Down
23 changes: 18 additions & 5 deletions packages/core/src/thebe/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import type { CodeBlock } from '../notebook';
import ThebeNotebook from '../notebook';
import type { INotebookContent } from '@jupyterlab/nbformat';
import type { Config } from '..';
import { makeConfiguration, ThebeEvents } from '..';
import { ThebeEvents } from '../events';
import { makeConfiguration } from '../options';
import { makeRenderMimeRegistry } from '../rendermime';
import * as coreModule from '../index';
import type { IRenderMimeRegistry } from '@jupyterlab/rendermime';

export function connectToBinder(config: Config): ThebeServer {
const server: ThebeServer = new ThebeServer(config);
Expand Down Expand Up @@ -35,21 +38,31 @@ export function makeServer(config: Config) {
return new ThebeServer(config);
}

export function setupNotebookFromBlocks(blocks: CodeBlock[], config: Config) {
return ThebeNotebook.fromCodeBlocks(blocks, config);
export function setupNotebookFromBlocks(
blocks: CodeBlock[],
config: Config,
rendermime: IRenderMimeRegistry,
) {
return ThebeNotebook.fromCodeBlocks(blocks, config, rendermime);
}

export function setupNotebookFromIpynb(ipynb: INotebookContent, config: Config) {
return ThebeNotebook.fromIpynb(ipynb, config);
export function setupNotebookFromIpynb(
ipynb: INotebookContent,
config: Config,
rendermime: IRenderMimeRegistry,
) {
return ThebeNotebook.fromIpynb(ipynb, config, rendermime);
}

export function setupThebeCore() {
console.log(`thebe:api:setupThebeCore`, { coreModule });
window.thebeCore = Object.assign(window.thebeCore ?? {}, {
module: coreModule,
api: {
makeConfiguration,
makeEvents,
makeServer,
makeRenderMimeRegistry,
connectToBinder,
connectToJupyter,
connectToJupyterLite,
Expand Down
14 changes: 12 additions & 2 deletions packages/core/src/thebe/entrypoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { ThebeEvents } from '../events';
import type { ThebeLiteGlobal } from 'thebe-lite';
import type * as coreModule from '../index';
import type { INotebookContent } from '@jupyterlab/nbformat';
import type { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { setupThebeCore } from './api';

/**
Expand All @@ -24,11 +25,20 @@ export interface JsApi {
makeEvents: () => ThebeEvents;
makeConfiguration: (options: Partial<CoreOptions>, events?: ThebeEvents) => Config;
makeServer: (config: Config) => ThebeServer;
makeRenderMimeRegistry: (mathjax?: coreModule.MathjaxOptions | undefined) => IRenderMimeRegistry;
connectToBinder: (config: Config) => ThebeServer;
connectToJupyter: (config: Config) => ThebeServer;
connectToJupyterLite: (config: Config) => ThebeServer;
setupNotebookFromBlocks: (blocks: CodeBlock[], config: Config) => ThebeNotebook;
setupNotebookFromIpynb: (ipynb: INotebookContent, config: Config) => ThebeNotebook;
setupNotebookFromBlocks: (
blocks: CodeBlock[],
config: Config,
rendermime: IRenderMimeRegistry,
) => ThebeNotebook;
setupNotebookFromIpynb: (
ipynb: INotebookContent,
config: Config,
rendermime: IRenderMimeRegistry,
) => ThebeNotebook;
}

export type ThebeCore = typeof coreModule;
Expand Down
Loading