浏览代码

Start moving to new printing system

Saul Shanabrook 6 年之前
父节点
当前提交
67dbe5d815

+ 6 - 5
packages/apputils/src/mainareawidget.ts

@@ -11,7 +11,7 @@ import { Toolbar } from './toolbar';
 
 import { DOMUtils } from './domutils';
 
-import { printSymbol, deferPrinting } from './printing';
+import { Printing } from './printing';
 
 /**
  * A widget meant to be contained in the JupyterLab main area.
@@ -22,7 +22,8 @@ import { printSymbol, deferPrinting } from './printing';
  * This widget is automatically disposed when closed.
  * This widget ensures its own focus when activated.
  */
-export class MainAreaWidget<T extends Widget = Widget> extends Widget {
+export class MainAreaWidget<T extends Widget = Widget> extends Widget
+  implements Printing.IProvidesHandler {
   /**
    * Construct a new main area widget.
    *
@@ -44,8 +45,6 @@ export class MainAreaWidget<T extends Widget = Widget> extends Widget {
     layout.addWidget(toolbar);
     layout.addWidget(content);
 
-    deferPrinting(this, content);
-
     if (!content.id) {
       content.id = DOMUtils.createDomID();
     }
@@ -109,7 +108,9 @@ export class MainAreaWidget<T extends Widget = Widget> extends Widget {
   /**
    * Print method. Defered to content.
    */
-  [printSymbol]: () => void;
+  [Printing.symbol](): Printing.OptionalAsyncThunk {
+    return Printing.retrievePrintFunction(this._content);
+  }
 
   /**
    * The content hosted by the widget.

+ 131 - 139
packages/apputils/src/printing.ts

@@ -20,167 +20,159 @@ import { Printd, PrintdCallback } from 'printd';
 import { Widget } from '@phosphor/widgets';
 import { Signal, ISignal } from '@phosphor/signaling';
 
-/**
- * Function that takes no arguments and when invoked prints out some object.
- */
-type PrintThunk = () => Promise<void>;
+export namespace Printing {
+  /**
+   * Function that takes no arguments and when invoked prints out some object or null if printing is not defined.
+   */
+  export type OptionalAsyncThunk = () => Promise<void> | null;
 
-/**
- * Function that takes in an object and returns a thunk that will print that object or null
- * if printing is not supported for that object.
- */
-type PrintFunction = (val: unknown) => PrintThunk | null;
+  /**
+   * Function that takes in an object and returns a function that prints it or null if it cannot be printed.
+   */
+  type Handler = (val: unknown) => OptionalAsyncThunk;
 
-/**
- * Combines two print functions into a resulting print function that calls both in sequence, returning
- * the first print function if it is returned.
- */
-function combinePrintFunctions(
-  f: PrintFunction,
-  g: PrintFunction
-): PrintFunction {
-  return (val: unknown) => {
-    const fRes = f(val);
-    if (fRes !== null) {
-      return fRes;
+  /**
+   * Combines two print creators so that both are called in sequence.
+   */
+  function combineHandlers(f: Handler, g: Handler): Handler {
+    return (val: unknown) => {
+      const fRes = f(val);
+      if (fRes !== null) {
+        return fRes;
+      }
+      return g(val);
+    };
+  }
+
+  function combineManyHandlers(fs: Iterable<Handler>): Handler {
+    return [...fs].reduce(combineHandlers, () => null);
+  }
+
+  class Registry {
+    constructor() {
+      this._handlerAdded = new Signal(this);
     }
-    return g(val);
-  };
-}
 
-function combineManyPrintFunctions(fs: Iterable<PrintFunction>): PrintFunction {
-  return [...fs].reduce(combinePrintFunctions, () => null);
-}
+    /**
+     * Adds a print function to the registry.
+     */
+    registerHandler(handlers: Handler) {
+      this._handlers.push(handlers);
+    }
 
-export class PrintRegistry {
-  constructor() {
-    this._printerAdded = new Signal(this);
+    /**
+     * Returns the print thunk for an object or null if it does not exist.
+     */
+    resolve(val: unknown): OptionalAsyncThunk {
+      return combineManyHandlers(this._handlers)(val);
+    }
+
+    /**
+     * Returns a signal that is triggered after a new printer is added.
+     */
+    get handlerAdded(): ISignal<Registry, Handler> {
+      return this._handlerAdded;
+    }
+
+    private readonly _handlers = new Array<Handler>();
+    private readonly _handlerAdded: Signal<Registry, Handler>;
   }
 
   /**
-   * Adds a print function to the registry.
+   * Export a global registry for printing.
+   *
+   * We could move this to an extension if we prefer.
    */
-  addPrintFunction(fn: PrintFunction) {
-    this._printers.push(fn);
-  }
+  export const registry = new Registry();
+  /**
+   * Symbol to use for a method that returns a function to print an object.
+   */
+  export const symbol = Symbol('print function');
 
   /**
-   * Returns the printer thunk for an object or null if it does not exist.
+   * Objects who provide a custom way of printing themselves
+   * should implement this interface.
    */
-  getPrinter(val: unknown): PrintThunk | null {
-    return combineManyPrintFunctions(this._printers)(val);
+  export interface IProvidesHandler {
+    /**
+     * Returns a function to print this object or null if it cannot be printed.
+     */
+    [symbol]: () => OptionalAsyncThunk;
   }
 
   /**
-   * Returns a signal that is triggered after a new printer is added.
+   * Returns whether an object implements a print method.
    */
-  get printerAdded(): ISignal<PrintRegistry, PrintFunction> {
-    return this._printerAdded;
+  export function providesHandler(a: any): a is IProvidesHandler {
+    try {
+      return symbol in a;
+    } catch {
+      // `in` raises a type error on non objects.
+      return false;
+    }
   }
 
-  private readonly _printers = new Array<PrintFunction>();
-  private readonly _printerAdded: Signal<PrintRegistry, PrintFunction>;
-}
-
-/**
- * Symbol to use for a method that prints out out the object.
- */
-export const printSymbol = Symbol();
-
-/**
- * Objects who provide a custom way of printing themselves
- * should implement this interface.
- */
-export interface IPrintable {
-  [printSymbol]: () => Promise<void>;
-}
-
-/**
- * Returns whether an object implements a print method.
- */
-export function isPrintable(a: any): a is IPrintable {
-  try {
-    return printSymbol in a;
-  } catch {
-    // `in` raises a type error on non objects.
-    return false;
+  /**
+   * Returns the print function for an object, or null if it does not provide a handler.
+   */
+  export function retrievePrintFunction(val: unknown): OptionalAsyncThunk {
+    if (providesHandler(val)) {
+      return val[symbol]();
+    }
+    return null;
   }
-}
 
-/**
- * Calls the print symbol on an object to print it.
- */
-export function printPrintable(a: IPrintable): Promise<void> {
-  return a[printSymbol]();
-}
+  /**
+   * Global print instance
+   */
+  const _PRINTD = new Printd();
 
-/**
- * Sets the print method on the parent to that of the child, if it
- * exists on the child.
- */
-export function delegatePrintMethod(parent: IPrintable, child: object) {
-  if (isPrintable(child)) {
-    parent[printSymbol] = child[printSymbol].bind(child);
+  /**
+   * Use this as a print property for a widget that will
+   * use the `printd` library to print the node, by
+   * creating an iframe and copying the DOM into it.
+   */
+  export function printWidget(
+    this: Widget,
+    cssText?: string,
+    callback?: PrintdCallback
+  ) {
+    // // reset URL if it's set.
+    // const iframe = _PRINTD.getIFrame();
+    // iframe.src = 'about://blank';
+
+    _PRINTD.print(this.node, [cssText], [], callback);
   }
-}
 
-export function printableFunction(val: unknown): PrintThunk | null {
-  if (!isPrintable(val)) {
-    return null;
+  /**
+   * Prints a URL by loading it into an iframe.
+   *
+   * @param url URL to load into an iframe.
+   */
+  export function printURL(url: string) {
+    _PRINTD.printURL('url');
+    // const iframe = _PRINTD.getIFrame();
+
+    // // print iframe after it loads new page
+    // iframe.addEventListener(
+    //   'load',
+    //   () => {
+    //     // copies logic from
+    //     // https://github.com/joseluisq/printd/blob/c7f05196da62d2f2be68386521c0af5908097253/src/index.ts#L62-L69
+    //     const result: boolean = iframe.contentWindow.document.execCommand(
+    //       'print',
+    //       false,
+    //       null
+    //     );
+
+    //     if (!result) {
+    //       iframe.contentWindow.print();
+    //     }
+    //   },
+    //   { once: true }
+    // );
+
+    // // load new page in iframe
+    // iframe.src = url;
   }
-  return () => printPrintable(val);
-}
-
-/**
- * Global print instance
- */
-const _PRINTD = new Printd();
-
-/**
- * Use this as a print property for a widget that will
- * use the `printd` library to print the node, by
- * creating an iframe and copying the DOM into it.
- */
-export function printWidget(
-  this: Widget,
-  cssText?: string,
-  callback?: PrintdCallback
-) {
-  // reset URL if it's set.
-  const iframe = _PRINTD.getIFrame();
-  iframe.src = 'about://blank';
-
-  _PRINTD.print(this.node, cssText, callback);
-}
-
-/**
- * Prints a URL by loading it into an iframe.
- *
- * NOTE: When https://github.com/joseluisq/printd/issues/20 is fixed this can be removed.
- * @param url URL to load into an iframe.
- */
-export function printURL(url: string) {
-  const iframe = _PRINTD.getIFrame();
-
-  // print iframe after it loads new page
-  iframe.addEventListener(
-    'load',
-    () => {
-      // copies logic from
-      // https://github.com/joseluisq/printd/blob/c7f05196da62d2f2be68386521c0af5908097253/src/index.ts#L62-L69
-      const result: boolean = iframe.contentWindow.document.execCommand(
-        'print',
-        false,
-        null
-      );
-
-      if (!result) {
-        iframe.contentWindow.print();
-      }
-    },
-    { once: true }
-  );
-
-  // load new page in iframe
-  iframe.src = url;
 }

+ 21 - 0
packages/coreutils/src/pageconfig.ts

@@ -154,6 +154,27 @@ export namespace PageConfig {
     return URLExt.normalize(wsUrl);
   }
 
+  /**
+   * Returns the URL converting this notebook to a certain
+   * format with nbconvert.
+   */
+  export function getNBConvertURL({
+    path,
+    format,
+    download
+  }: {
+    path: string;
+    format: string;
+    download: boolean;
+  }): string {
+    const notebookPath = URLExt.encodeParts(path);
+    const url = URLExt.join(getBaseUrl(), 'nbconvert', format, notebookPath);
+    if (download) {
+      return url + '?download=true';
+    }
+    return url;
+  }
+
   /**
    * Get the authorization token for a Jupyter application.
    */

+ 6 - 8
packages/docmanager-extension/src/index.ts

@@ -17,8 +17,7 @@ import {
   showErrorMessage,
   Dialog,
   ICommandPalette,
-  isPrintable,
-  print
+  Printing
 } from '@jupyterlab/apputils';
 
 import { IChangedArgs, ISettingRegistry, Time } from '@jupyterlab/coreutils';
@@ -42,8 +41,6 @@ import { IStatusBar } from '@jupyterlab/statusbar';
 
 import { IDisposable } from '@phosphor/disposable';
 
-const _PRINTD = new Printd();
-
 /**
  * The command IDs used by the document manager plugin.
  */
@@ -787,13 +784,14 @@ function addLabCommands(
   commands.addCommand(CommandIDs.print, {
     label: 'Print...',
     isEnabled: () => {
-      const { currentWidget } = shell;
-      return isPrintable(currentWidget);
+      const { currentWidget } = labShell;
+      return Printing.registry.resolve(currentWidget) !== null;
     },
     execute: () => {
       const widget = contextMenuWidget();
-      if (isPrintable(widget)) {
-        print(widget);
+      const printFunction = Printing.registry.resolve(widget);
+      if (printFunction) {
+        printFunction();
       }
     }
   });

+ 1 - 1
packages/json-extension/package.json

@@ -30,7 +30,7 @@
     "watch": "tsc -b --watch"
   },
   "dependencies": {
-    "@jupyterlab/apputils": "^0.19.1",
+    "@jupyterlab/apputils": "^1.0.0-alpha.3",
     "@jupyterlab/rendermime-interfaces": "^1.3.0-alpha.3",
     "@jupyterlab/ui-components": "^1.0.0-alpha.3",
     "@phosphor/coreutils": "^1.3.0",

+ 2 - 5
packages/json-extension/tsconfig.json

@@ -10,13 +10,10 @@
       "path": "../apputils"
     },
     {
-      "path": "../ui-components",
-    },
-    {
-      "path": "../apputils"
+      "path": "../rendermime-interfaces"
     },
     {
-      "path": "../rendermime-interfaces"
+      "path": "../ui-components"
     }
   ]
 }

+ 5 - 3
packages/notebook-extension/src/index.ts

@@ -277,7 +277,6 @@ const factory: JupyterFrontEndPlugin<NotebookPanel.IContentFactory> = {
   autoStart: true,
   activate: (app: JupyterFrontEnd, editorServices: IEditorServices) => {
     let editorFactory = editorServices.factoryService.newInlineEditor;
-    NotebookPanel.prototype[printSymbol] = () => {};
     return new NotebookPanel.ContentFactory({ editorFactory });
   }
 };
@@ -503,7 +502,6 @@ function activateNotebookHandler(
     modelName: 'notebook',
     defaultFor: ['notebook'],
     preferKernel: true,
-    baseUrl: services.serverSettings.baseUrl,
     canStartKernel: true,
     rendermime: rendermime,
     contentFactory,
@@ -1095,7 +1093,11 @@ function addCommands(
         return;
       }
 
-      const url = current.getNBConvertURL(args['format'] as string);
+      const url = PageConfig.getNBConvertURL({
+        format: args['format'] as string,
+        download: true,
+        path: current.context.path
+      });
       const child = window.open('', '_blank');
       const { context } = current;
 

+ 16 - 41
packages/notebook/src/panel.ts

@@ -9,7 +9,7 @@ import { Message } from '@phosphor/messaging';
 
 import { ISignal, Signal } from '@phosphor/signaling';
 
-import { IClientSession, printSymbol, printURL } from '@jupyterlab/apputils';
+import { IClientSession, Printing } from '@jupyterlab/apputils';
 
 import { DocumentWidget } from '@jupyterlab/docregistry';
 
@@ -18,7 +18,7 @@ import { RenderMimeRegistry } from '@jupyterlab/rendermime';
 import { INotebookModel } from './model';
 
 import { Notebook } from './widget';
-import { URLExt } from '@jupyterlab/coreutils';
+import { PageConfig } from '@jupyterlab/coreutils';
 
 /**
  * The class name added to notebook panels.
@@ -55,12 +55,6 @@ export class NotebookPanel extends DocumentWidget<Notebook, INotebookModel> {
       this
     );
 
-    this._baseUrl = options.content.baseUrl;
-
-    if (this._baseUrl !== undefined) {
-      this[printSymbol] = this._print;
-    }
-
     this.revealed.then(() => {
       // Set the document edit mode on initial open if it looks like a new document.
       if (this.content.widgets.length === 1) {
@@ -135,28 +129,6 @@ export class NotebookPanel extends DocumentWidget<Notebook, INotebookModel> {
     super.dispose();
   }
 
-  /**
-   * Returns the URL converting this notebook to a certain
-   * format with nbconvert.
-   *
-   * If the `baseUrl` was not passed into the panel, then
-   * undefined is returned.
-   */
-  getNBConvertURL(
-    format: string,
-    download: boolean = true
-  ): string | undefined {
-    if (this._baseUrl === undefined) {
-      return undefined;
-    }
-    const notebookPath = URLExt.encodeParts(this.context.path);
-    const url = URLExt.join(this._baseUrl, 'nbconvert', format, notebookPath);
-    if (download) {
-      return url + '?download=true';
-    }
-    return url;
-  }
-
   /**
    * Handle `'activate-request'` messages.
    */
@@ -169,18 +141,22 @@ export class NotebookPanel extends DocumentWidget<Notebook, INotebookModel> {
 
   /**
    * Prints the notebook by converting to HTML with nbconvert.
-   *
-   * Ideally the name of this method would be `[printSymbol]`, but
-   * typescript won't let us make this a method because our superclass
-   * has it as an assigned property.
    */
-  private async _print() {
-    // Save before generating HTML
-    if (this.context.model.dirty && !this.context.model.readOnly) {
-      await this.context.save();
-    }
+  [Printing.symbol]() {
+    return async () => {
+      // Save before generating HTML
+      if (this.context.model.dirty && !this.context.model.readOnly) {
+        await this.context.save();
+      }
 
-    printURL(this.getNBConvertURL('html', false));
+      Printing.printURL(
+        PageConfig.getNBConvertURL({
+          format: 'html',
+          download: false,
+          path: this.context.path
+        })
+      );
+    };
   }
 
   /**
@@ -226,7 +202,6 @@ export class NotebookPanel extends DocumentWidget<Notebook, INotebookModel> {
   }
 
   private _activated = new Signal<this, void>(this);
-  private _baseUrl?: string;
 }
 
 /**

+ 3 - 3
yarn.lock

@@ -8061,9 +8061,9 @@ pretty-format@^23.6.0:
     ansi-regex "^3.0.0"
     ansi-styles "^3.2.0"
 
-printd@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/printd/-/printd-1.0.1.tgz#fddfb374b14130d8aa31f02734e3a0f103f6ad98"
+printd@1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/printd/-/printd-1.3.0.tgz#090f3293a7aef536894630967b91d1b0598574ce"
 
 private@^0.1.6, private@^0.1.8, private@~0.1.5:
   version "0.1.8"