import { ChildrenLike, VirtualDOM } from '@youwol/rx-vdom' import type { MdParsingOptions, ViewGenerator } from '../markdown' import { Router } from '../router' import { from, of, take } from 'rxjs' import { Scope, State } from './state' import { DisplayFactory } from './display-utils' import { Dependencies } from './index' /** * The common set for attributes of a notebook cell. * * When provided from a DOM element in the markdown source, they are defined using kebab case: * ``` * * ``` */ export type CellCommonAttributes = { /** * Whether to display line numbers on cell. */ lineNumbers?: boolean /** * Whether the cell is readonly. */ readOnly?: boolean } /** * Default values for {@link CellCommonAttributes}. */ export const defaultCellAttributes: CellCommonAttributes = { lineNumbers: false, readOnly: false, } /** * Global options for a {@link NotebookPage}. */ export type NotebookOptions = { /** * Whether to run all the cells of a notebook page when loaded. */ runAtStart?: boolean /** * The default values for cell's attribute. */ defaultCellAttributes?: CellCommonAttributes /** * Options for markdown parsing. */ markdown?: MdParsingOptions } export const notebookViews = ({ state }: { state: State }) => { return { 'cell-output': (elem: HTMLElement) => { return state.createDeportedOutputsView(elem) }, 'js-cell': (elem: HTMLElement) => { return state.createJsCell(elem) }, 'md-cell': (elem: HTMLElement, parserOptions: MdParsingOptions) => { return state.createMdCell(elem, parserOptions) }, 'py-cell': (elem: HTMLElement) => { return state.createPyCell(elem) }, 'interpreter-cell': (elem: HTMLElement) => { return state.createInterpreterCell(elem) }, 'worker-cell': (elem: HTMLElement) => { return state.createWorkerCell(elem) }, } } /** * Represents a page of a notebook. * * A notebook page is a markdown content including definition of executable cells * (*e.g.* {@link JsCellView}, {@link MdCellView}) as well as other related components. * * Cells run in the order of inclusion, and share their top level scope. */ export class NotebookPage implements VirtualDOM<'div'> { public readonly tag = 'div' /** * Classes associated to the view. */ public readonly class = 'mknb-NotebookPage' public readonly url: string public readonly views: { [k: string]: ViewGenerator } public readonly router: Router public readonly children: ChildrenLike = [] /** * State manager. */ public readonly state: State public readonly options: NotebookOptions /** * Constructs the page. * * @param params The parameters * @param params.url Url pointing to the markdown content, only used if the `src` attribute is not provided. * @param params.src Markdown source content. To fetch from a URL leave it empty & provide instead * the `url` attribute. * @param params.router Application's router. * @param params.initialScope Initial scope provided to the first executing cell. * @param params.displayFactory Additional custom {@link DisplayFactory} invoked when `display` is used. * @param params.options Global options for the page, in particular defined the default attribute for the various * cells. */ constructor(params: { url?: string src?: string router: Router initialScope?: Partial displayFactory?: DisplayFactory options?: NotebookOptions }) { Object.assign(this, params) if (params.src === undefined && params.url === undefined) { console.error( 'Neither url or src parameter provided to the notebook page.', ) return } this.state = new State({ router: this.router, displayFactory: params.displayFactory, initialScope: params.initialScope, }) const source$ = params.src !== undefined ? of(params.src) : from(fetch(this.url).then((resp) => resp.text())).pipe( take(1), ) this.children = [ { source$, vdomMap: (src: string) => { const vdom = Dependencies.parseMd({ src, router: this.router, ...(this.options?.markdown || {}), views: { ...(this.options?.markdown?.views || {}), ...notebookViews({ state: this.state, }), }, }) if (params?.options?.runAtStart) { this.state.execute(this.state.ids.slice(-1)[0]).then() } return vdom }, }, ] } }