From 1ae09e1c0c15eebb841c5139b366de27bfb875d4 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Thu, 9 Jan 2020 16:46:39 +0100 Subject: [PATCH 1/5] Split base manager into separate packages --- .github/workflows/testjs.yml | 4 + packages/base-manager/.gitignore | 2 + packages/base-manager/.jshintrc | 5 + packages/base-manager/.npmignore | 7 + packages/base-manager/package.json | 67 +++++++ packages/base-manager/src/index.ts | 5 + .../src/manager-base.ts | 144 ++------------ packages/base-manager/src/utils.ts | 67 +++++++ packages/base-manager/test/karma-cov.conf.js | 22 +++ packages/base-manager/test/karma.conf.js | 13 ++ .../base-manager/test/src/dummy-manager.ts | 180 ++++++++++++++++++ packages/base-manager/test/src/index.ts | 4 + .../test/src/manager_test.ts | 2 +- packages/base-manager/test/tsconfig.json | 16 ++ .../base-manager/test/webpack-cov.conf.js | 28 +++ packages/base-manager/test/webpack.conf.js | 20 ++ packages/base-manager/tsconfig.json | 15 ++ packages/base/src/index.ts | 1 - packages/base/src/utils.ts | 64 ------- packages/base/src/widget.ts | 125 +++++++++++- packages/base/test/src/dummy-manager.ts | 151 +++++++++++++-- packages/base/test/src/index.ts | 1 - packages/controls/test/src/dummy-manager.ts | 3 +- packages/html-manager/src/htmlmanager.ts | 3 +- packages/jupyterlab-manager/package.json | 1 + packages/jupyterlab-manager/src/manager.ts | 8 +- widgetsnbextension/src/manager.js | 5 +- 27 files changed, 743 insertions(+), 220 deletions(-) create mode 100644 packages/base-manager/.gitignore create mode 100644 packages/base-manager/.jshintrc create mode 100644 packages/base-manager/.npmignore create mode 100644 packages/base-manager/package.json create mode 100644 packages/base-manager/src/index.ts rename packages/{base => base-manager}/src/manager-base.ts (85%) create mode 100644 packages/base-manager/src/utils.ts create mode 100644 packages/base-manager/test/karma-cov.conf.js create mode 100644 packages/base-manager/test/karma.conf.js create mode 100644 packages/base-manager/test/src/dummy-manager.ts create mode 100644 packages/base-manager/test/src/index.ts rename packages/{base => base-manager}/test/src/manager_test.ts (99%) create mode 100644 packages/base-manager/test/tsconfig.json create mode 100644 packages/base-manager/test/webpack-cov.conf.js create mode 100644 packages/base-manager/test/webpack.conf.js create mode 100644 packages/base-manager/tsconfig.json diff --git a/.github/workflows/testjs.yml b/.github/workflows/testjs.yml index 1b0d3083ef..892169b8e4 100644 --- a/.github/workflows/testjs.yml +++ b/.github/workflows/testjs.yml @@ -34,6 +34,10 @@ jobs: yarn run test:unit:firefox:headless popd + pushd packages/base-manager + yarn run test:unit:firefox:headless + popd + pushd packages/controls yarn run test:unit:firefox:headless popd diff --git a/packages/base-manager/.gitignore b/packages/base-manager/.gitignore new file mode 100644 index 0000000000..c0c6e3048b --- /dev/null +++ b/packages/base-manager/.gitignore @@ -0,0 +1,2 @@ +lib/ +test/coverage/ diff --git a/packages/base-manager/.jshintrc b/packages/base-manager/.jshintrc new file mode 100644 index 0000000000..1799b2870f --- /dev/null +++ b/packages/base-manager/.jshintrc @@ -0,0 +1,5 @@ +{ + "esnext": true, + "expr": true, + "node": true +} diff --git a/packages/base-manager/.npmignore b/packages/base-manager/.npmignore new file mode 100644 index 0000000000..7c04752fea --- /dev/null +++ b/packages/base-manager/.npmignore @@ -0,0 +1,7 @@ +.DS_Store +node_modules/ +test/ +.jshintrc +karma.conf.js +build_css.js +examples/ diff --git a/packages/base-manager/package.json b/packages/base-manager/package.json new file mode 100644 index 0000000000..efe5ad62da --- /dev/null +++ b/packages/base-manager/package.json @@ -0,0 +1,67 @@ +{ + "name": "@jupyter-widgets/base-manager", + "version": "1.0.0", + "description": "Jupyter interactive widgets - base manager", + "repository": { + "type": "git", + "url": "https://github.com/jupyter-widgets/ipywidgets.git" + }, + "license": "BSD-3-Clause", + "author": "Project Jupyter", + "files": [ + "lib/**/*.d.ts", + "lib/**/*.js", + "css/*.css" + ], + "main": "lib/index.js", + "typings": "lib/index.d.ts", + "scripts": { + "build": "npm run build:src", + "build:src": "tsc --build", + "build:test": "tsc --build test && webpack --config test/webpack.conf.js", + "clean": "npm run clean:src", + "clean:src": "rimraf lib && rimraf tsconfig.tsbuildinfo", + "prepublish": "npm run clean && npm run build", + "test": "npm run test:unit", + "test:coverage": "npm run build:test && webpack --config test/webpack-cov.conf.js && karma start test/karma-cov.conf.js", + "test:unit": "npm run test:unit:firefox && npm run test:unit:chrome", + "test:unit:chrome": "npm run test:unit:default -- --browsers=Chrome", + "test:unit:default": "npm run build:test && karma start test/karma.conf.js --log-level debug", + "test:unit:firefox": "npm run test:unit:default -- --browsers=Firefox", + "test:unit:firefox:headless": "npm run test:unit:default -- --browsers=FirefoxHeadless", + "test:unit:ie": "npm run test:unit:default -- --browsers=IE" + }, + "dependencies": { + "@jupyterlab/services": "^5.0.0-beta.2", + "@lumino/coreutils": "^1.2.0", + "base64-js": "^1.2.1" + }, + "devDependencies": { + "@types/base64-js": "^1.2.5", + "@types/chai": "^4.1.7", + "@types/chai-as-promised": "^7.1.0", + "@types/expect.js": "^0.3.29", + "@types/mocha": "^5.2.7", + "@types/sinon": "^7.0.13", + "@types/sinon-chai": "^3.2.2", + "chai": "^4.0.0", + "chai-as-promised": "^7.0.0", + "expect.js": "^0.3.1", + "istanbul-instrumenter-loader": "^3.0.1", + "karma": "^4.1.0", + "karma-chrome-launcher": "^2.2.0", + "karma-coverage": "^1.1.2", + "karma-firefox-launcher": "^1.1.0", + "karma-ie-launcher": "^1.0.0", + "karma-mocha": "^1.3.0", + "karma-mocha-reporter": "^2.2.5", + "karma-webpack": "^4.0.2", + "mocha": "^6.1.4", + "npm-run-all": "^4.1.5", + "rimraf": "^2.6.1", + "sinon": "^7.3.2", + "sinon-chai": "^3.3.0", + "typescript": "~3.7.4", + "webpack": "^4.41.5" + } +} diff --git a/packages/base-manager/src/index.ts b/packages/base-manager/src/index.ts new file mode 100644 index 0000000000..f57178ce62 --- /dev/null +++ b/packages/base-manager/src/index.ts @@ -0,0 +1,5 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +export * from './manager-base'; +export * from './utils'; diff --git a/packages/base/src/manager-base.ts b/packages/base-manager/src/manager-base.ts similarity index 85% rename from packages/base/src/manager-base.ts rename to packages/base-manager/src/manager-base.ts index 0da54a8d58..f56b43c2f8 100644 --- a/packages/base/src/manager-base.ts +++ b/packages/base-manager/src/manager-base.ts @@ -1,132 +1,28 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import * as utils from './utils'; import * as services from '@jupyterlab/services'; import { JSONObject, PartialJSONObject } from '@lumino/coreutils'; -import { - DOMWidgetView, WidgetModel, WidgetView, DOMWidgetModel -} from './widget'; import { - IClassicComm, ICallbacks -} from './services-shim'; + DOMWidgetView, WidgetModel, WidgetView, DOMWidgetModel, + IClassicComm, ICallbacks, + put_buffers, remove_buffers, resolvePromisesDict, + ISerializedState, reject, uuid, PROTOCOL_VERSION, + IWidgetManager, IModelOptions, IWidgetOptions +} from '@jupyter-widgets/base'; import { - ISerializedState + base64ToBuffer, bufferToBase64, hexToBuffer } from './utils'; -import { - PROTOCOL_VERSION -} from './version'; const PROTOCOL_MAJOR_VERSION = PROTOCOL_VERSION.split('.', 1)[0]; -/** - * The options for a model. - * - * #### Notes - * Either a comm or a model_id must be provided. - */ -export -interface IModelOptions { - - /** - * Target name of the widget model to create. - */ - model_name: string; - - /** - * Module name of the widget model to create. - */ - model_module: string; - - /** - * Semver version requirement for the model module. - */ - model_module_version: string; - - /** - * Target name of the widget view to create. - */ - view_name?: string | null; - - /** - * Module name of the widget view to create. - */ - view_module?: string | null; - - /** - * Semver version requirement for the view module. - */ - view_module_version?: string; - - /** - * Comm object associated with the widget. - */ - comm?: any; - - /** - * The model id to use. If not provided, the comm id of the comm is used. - */ - model_id?: string; -} - -/** - * The options for a connected model. - * - * This gives all of the information needed to instantiate a comm to a new - * widget on the kernel side (so view information is mandatory). - * - * #### Notes - * Either a comm or a model_id must be provided. - */ -export -interface IWidgetOptions extends IModelOptions { - /** - * Target name of the widget model to create. - */ - model_name: string; - - /** - * Module name of the widget model to create. - */ - model_module: string; - - /** - * Semver version requirement for the model module. - */ - model_module_version: string; - - /** - * Target name of the widget view to create. - */ - view_name: string | null; - - /** - * Module name of the widget view to create. - */ - view_module: string | null; - - /** - * Semver version requirement for the view module. - */ - view_module_version: string; - - /** - * Comm object associated with the widget. - */ - comm?: IClassicComm; - - /** - * The model id to use. If not provided, the comm id of the comm is used. - */ - model_id?: string; -} export interface IState extends PartialJSONObject { @@ -160,14 +56,14 @@ interface IBase64Buffers extends PartialJSONObject { /** * Manager abstract base class */ -export abstract class ManagerBase { +export abstract class ManagerBase implements IWidgetManager { /** * Display a DOMWidgetView for a particular model. */ display_model(msg: services.KernelMessage.IMessage | null, model: DOMWidgetModel, options: any = {}): Promise { return this.create_view(model, options).then( - view => this.display_view(msg, view, options)).catch(utils.reject('Could not create view', true)); + view => this.display_view(msg, view, options)).catch(reject('Could not create view', true)); } /** @@ -206,9 +102,9 @@ export abstract class ManagerBase { }); view.listenTo(model, 'destroy', view.remove); return Promise.resolve(view.render()).then(() => { return view; }); - }).catch(utils.reject('Could not create a view for model id ' + model.model_id, true)); + }).catch(reject('Could not create a view for model id ' + model.model_id, true)); }); - const id = utils.uuid(); + const id = uuid(); model.views[id] = viewPromise; viewPromise.then((view) => { view.once('remove', () => { delete view.model.views[id]; }, this); @@ -219,7 +115,7 @@ export abstract class ManagerBase { /** * callback handlers specific to a view */ - callbacks (view?: WidgetView): ICallbacks { + callbacks(view?: WidgetView): ICallbacks { return {}; } @@ -258,13 +154,13 @@ export abstract class ManagerBase { return new DataView(b instanceof ArrayBuffer ? b : b.buffer); } }); - utils.put_buffers(data.state, buffer_paths, buffers); + put_buffers(data.state, buffer_paths, buffers); return this.new_model({ model_name: data.state['_model_name'] as string, model_module: data.state['_model_module'] as string, model_module_version: data.state['_model_module_version'] as string, comm: comm - }, data.state).catch(utils.reject('Could not create a model.', true)); + }, data.state).catch(reject('Could not create a model.', true)); } /** @@ -318,7 +214,7 @@ export abstract class ManagerBase { }, () => { // Comm Promise Rejected. if (!options_clone.model_id) { - options_clone.model_id = utils.uuid(); + options_clone.model_id = uuid(); } return this.new_model(options_clone, serialized_state); }); @@ -402,7 +298,7 @@ export abstract class ManagerBase { * @return Promise that resolves when the widget state is cleared. */ clear_state(): Promise { - return utils.resolvePromisesDict(this._models).then((models) => { + return resolvePromisesDict(this._models).then((models) => { Object.keys(models).forEach(id => models[id].close()); this._models = Object.create(null); }); @@ -454,14 +350,14 @@ export abstract class ManagerBase { return Promise.all(Object.keys(models).map(model_id => { // First put back the binary buffers - const decode: { [s: string]: (s: string) => ArrayBuffer } = {'base64': utils.base64ToBuffer, 'hex': utils.hexToBuffer}; + const decode: { [s: string]: (s: string) => ArrayBuffer } = {'base64': base64ToBuffer, 'hex': hexToBuffer}; const model = models[model_id]; const modelState = model.state; if (model.buffers) { const bufferPaths = model.buffers.map((b: any) => b.path); // put_buffers expects buffers to be DataViews const buffers = model.buffers.map((b: any) => new DataView(decode[b.encoding](b.data))); - utils.put_buffers(model.state, bufferPaths, buffers); + put_buffers(model.state, bufferPaths, buffers); } // If the model has already been created, set its state and then @@ -598,10 +494,10 @@ function serialize_state(models: WidgetModel[], options: IStateOptions = {}): IM const state: IManagerStateMap = {}; models.forEach(model => { const model_id = model.model_id; - const split = utils.remove_buffers(model.serialize(model.get_state(options.drop_defaults))); + const split = remove_buffers(model.serialize(model.get_state(options.drop_defaults))); const buffers: IBase64Buffers[] = split.buffers.map((buffer, index) => { return { - data: utils.bufferToBase64(buffer), + data: bufferToBase64(buffer), path: split.buffer_paths[index], encoding: 'base64' }; diff --git a/packages/base-manager/src/utils.ts b/packages/base-manager/src/utils.ts new file mode 100644 index 0000000000..4af72f9efc --- /dev/null +++ b/packages/base-manager/src/utils.ts @@ -0,0 +1,67 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { + toByteArray, fromByteArray +} from 'base64-js'; + + +const hexTable = [ + '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0A', '0B', '0C', '0D', '0E', '0F', + '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1A', '1B', '1C', '1D', '1E', '1F', + '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2A', '2B', '2C', '2D', '2E', '2F', + '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3A', '3B', '3C', '3D', '3E', '3F', + '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4A', '4B', '4C', '4D', '4E', '4F', + '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5A', '5B', '5C', '5D', '5E', '5F', + '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6A', '6B', '6C', '6D', '6E', '6F', + '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7A', '7B', '7C', '7D', '7E', '7F', + '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8A', '8B', '8C', '8D', '8E', '8F', + '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9A', '9B', '9C', '9D', '9E', '9F', + 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9', 'AA', 'AB', 'AC', 'AD', 'AE', 'AF', + 'B0', 'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9', 'BA', 'BB', 'BC', 'BD', 'BE', 'BF', + 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9', 'CA', 'CB', 'CC', 'CD', 'CE', 'CF', + 'D0', 'D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9', 'DA', 'DB', 'DC', 'DD', 'DE', 'DF', + 'E0', 'E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9', 'EA', 'EB', 'EC', 'ED', 'EE', 'EF', + 'F0', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'FA', 'FB', 'FC', 'FD', 'FE', 'FF' +]; + +/** + * Convert an ArrayBuffer to a hex string. + */ +export +function bufferToHex(buffer: ArrayBuffer): string { + const x = new Uint8Array(buffer); + const s = []; + for (let i = 0; i < x.length; i++) { + s.push(hexTable[x[i]]); + } + return s.join(''); +} + +/** + * Convert a hex string to an ArrayBuffer. + */ +export +function hexToBuffer(hex: string): ArrayBuffer { + const x = new Uint8Array(hex.length / 2); + for (let i = 0; i < hex.length; i += 2) { + x[i / 2] = parseInt(hex.slice(i, i + 2), 16); + } + return x.buffer; +} + +/** + * Convert an ArrayBuffer to a base64 string. + */ +export +function bufferToBase64(buffer: ArrayBuffer): string { + return fromByteArray(new Uint8Array(buffer)); +} + +/** + * Convert a base64 string to an ArrayBuffer. + */ +export +function base64ToBuffer(base64: string): ArrayBuffer { + return toByteArray(base64).buffer; +} diff --git a/packages/base-manager/test/karma-cov.conf.js b/packages/base-manager/test/karma-cov.conf.js new file mode 100644 index 0000000000..47ec79cdc4 --- /dev/null +++ b/packages/base-manager/test/karma-cov.conf.js @@ -0,0 +1,22 @@ +var path = require('path'); + +module.exports = function (config) { + config.set({ + basePath: '..', + browsers: ['Firefox'], + frameworks: ['mocha'], + reporters: ['mocha', 'coverage'], + files: ['test/build/coverage.js'], + coverageReporter: { + reporters : [ + { 'type': 'text' }, + { 'type': 'lcov', dir: 'test/coverage' }, + { 'type': 'html', dir: 'test/coverage' } + ] + }, + port: 9876, + colors: true, + singleRun: true, + logLevel: config.LOG_INFO + }); +}; diff --git a/packages/base-manager/test/karma.conf.js b/packages/base-manager/test/karma.conf.js new file mode 100644 index 0000000000..4098dfbdd8 --- /dev/null +++ b/packages/base-manager/test/karma.conf.js @@ -0,0 +1,13 @@ +module.exports = function (config) { + config.set({ + basePath: '..', + frameworks: ['mocha'], + reporters: ['mocha'], + files: ['test/build/bundle.js'], + port: 9876, + colors: true, + singleRun: true, + logLevel: config.LOG_INFO, + browserNoActivityTimeout: 30000 + }); +}; diff --git a/packages/base-manager/test/src/dummy-manager.ts b/packages/base-manager/test/src/dummy-manager.ts new file mode 100644 index 0000000000..9f12d47dda --- /dev/null +++ b/packages/base-manager/test/src/dummy-manager.ts @@ -0,0 +1,180 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import * as widgets from '@jupyter-widgets/base'; +import * as services from '@jupyterlab/services'; +import * as Backbone from 'backbone'; + +import { ManagerBase } from '../../lib'; + +import * as sinon from 'sinon'; +void sinon; + +let numComms = 0; + +export +class MockComm implements widgets.IClassicComm { + constructor() { + this.comm_id = `mock-comm-id-${numComms}`; + numComms += 1; + } + on_open(fn: Function): void { + this._on_open = fn; + } + on_close(fn: Function): void { + this._on_close = fn; + } + on_msg(fn: Function): void { + this._on_msg = fn; + } + _process_msg(msg: any): any { + if (this._on_msg) { + return this._on_msg(msg); + } else { + return Promise.resolve(); + } + } + open(): string { + if (this._on_open) { + this._on_open(); + } + return ''; + } + close(): string { + if (this._on_close) { + this._on_close(); + } + return ''; + } + send(): string { + return ''; + } + comm_id: string; + target_name: string; + _on_msg: Function | null = null; + _on_open: Function | null = null; + _on_close: Function | null = null; +} + +const typesToArray: {[key: string]: any} = { + int8: Int8Array, + int16: Int16Array, + int32: Int32Array, + uint8: Uint8Array, + uint16: Uint16Array, + uint32: Uint32Array, + float32: Float32Array, + float64: Float64Array +}; + + +const JSONToArray = function(obj: any): any { + return new typesToArray[obj.dtype](obj.buffer.buffer); +}; + +const arrayToJSON = function(obj: any): any { + const dtype = Object.keys(typesToArray).filter( + i => typesToArray[i] === obj.constructor)[0]; + return {dtype, buffer: obj}; +}; + +const array_serialization = { + deserialize: JSONToArray, + serialize: arrayToJSON +}; + + +class TestWidget extends widgets.WidgetModel { + defaults(): Backbone.ObjectHash { + return {...super.defaults(), + _model_module: 'test-widgets', + _model_name: 'TestWidget', + _model_module_version: '1.0.0', + _view_module: 'test-widgets', + _view_name: 'TestWidgetView', + _view_module_version: '1.0.0', + _view_count: null as any, + }; + } +} + +class TestWidgetView extends widgets.WidgetView { + render(): void { + this._rendered += 1; + super.render(); + } + remove(): void { + this._removed += 1; + super.remove(); + } + _removed = 0; + _rendered = 0; +} + +class BinaryWidget extends TestWidget { + static serializers = { + ...widgets.WidgetModel.serializers, + array: array_serialization + }; + defaults(): Backbone.ObjectHash { + return {...super.defaults(), + _model_name: 'BinaryWidget', + _view_name: 'BinaryWidgetView', + array: new Int8Array(0)}; + } +} + +class BinaryWidgetView extends TestWidgetView { + render(): void { + this._rendered += 1; + } + _rendered = 0; +} + +const testWidgets = {TestWidget, TestWidgetView, BinaryWidget, BinaryWidgetView}; + +export +class DummyManager extends ManagerBase { + constructor() { + super(); + this.el = window.document.createElement('div'); + } + + display_view(msg: services.KernelMessage.IMessage, view: Backbone.View, options: any): Promise { + // TODO: make this a spy + // TODO: return an html element + return Promise.resolve(view).then(view => { + this.el.appendChild(view.el); + view.on('remove', () => console.log('view removed', view)); + return view.el; + }); + } + + protected loadClass(className: string, moduleName: string, moduleVersion: string): Promise { + if (moduleName === '@jupyter-widgets/base') { + if ((widgets as any)[className]) { + return Promise.resolve((widgets as any)[className]); + } else { + return Promise.reject(`Cannot find class ${className}`); + } + } else if (moduleName === 'test-widgets') { + if ((testWidgets as any)[className]) { + return Promise.resolve((testWidgets as any)[className]); + } else { + return Promise.reject(`Cannot find class ${className}`); + } + } else { + return Promise.reject(`Cannot find module ${moduleName}`); + } + } + + _get_comm_info(): Promise<{}> { + return Promise.resolve({}); + } + + _create_comm(): Promise { + return Promise.resolve(new MockComm()); + } + + el: HTMLElement; +} diff --git a/packages/base-manager/test/src/index.ts b/packages/base-manager/test/src/index.ts new file mode 100644 index 0000000000..b0b9f19263 --- /dev/null +++ b/packages/base-manager/test/src/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import './manager_test'; diff --git a/packages/base/test/src/manager_test.ts b/packages/base-manager/test/src/manager_test.ts similarity index 99% rename from packages/base/test/src/manager_test.ts rename to packages/base-manager/test/src/manager_test.ts index 84b81198a5..90d3188e6e 100644 --- a/packages/base/test/src/manager_test.ts +++ b/packages/base-manager/test/src/manager_test.ts @@ -2,7 +2,7 @@ import { DummyManager, MockComm } from './dummy-manager'; -import * as widgets from '../../lib'; +import * as widgets from '@jupyter-widgets/base'; import { expect diff --git a/packages/base-manager/test/tsconfig.json b/packages/base-manager/test/tsconfig.json new file mode 100644 index 0000000000..289cf6a323 --- /dev/null +++ b/packages/base-manager/test/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../tsconfigbase", + "compilerOptions": { + "types": ["mocha"], + "outDir": "build", + "rootDir": "src", + "lib": ["dom", "es5", "es2015.promise", "es2015.iterable"], + "target": "es5" + }, + "include": ["src/*"], + "references": [ + { + "path": ".." + } + ] +} diff --git a/packages/base-manager/test/webpack-cov.conf.js b/packages/base-manager/test/webpack-cov.conf.js new file mode 100644 index 0000000000..6ba0dc8e58 --- /dev/null +++ b/packages/base-manager/test/webpack-cov.conf.js @@ -0,0 +1,28 @@ +var path = require('path'); + +module.exports = { + entry: './test/build/index.js', + output: { + path: __dirname + "/build", + filename: "coverage.js", + publicPath: "./build/" + }, + bail: true, + module: { + loaders: [ + { test: /\.json$/, loader: 'json-loader' }, + { test: /\.ipynb$/, loader: 'json-loader' }, + { test: /\.css$/, loader: 'style-loader!css-loader' }, + { test: /\.md$/, loader: 'raw-loader'}, + { test: /\.html$/, loader: "file?name=[name].[ext]" } + ], + preLoaders: [ + // instrument only testing sources with Istanbul + { + test: /\.js$/, + include: path.resolve('lib/'), + loader: 'istanbul-instrumenter' + } + ] + } +} diff --git a/packages/base-manager/test/webpack.conf.js b/packages/base-manager/test/webpack.conf.js new file mode 100644 index 0000000000..0c51f922ce --- /dev/null +++ b/packages/base-manager/test/webpack.conf.js @@ -0,0 +1,20 @@ +var path = require('path'); + +module.exports = { + entry: './test/build/index.js', + output: { + path: __dirname + "/build", + filename: "bundle.js", + publicPath: "./build/" + }, + bail: true, + module: { + rules: [ + { test: /\.css$/, loader: 'style-loader!css-loader' }, + { test: /\.md$/, loader: 'raw-loader'}, + { test: /\.html$/, loader: "file?name=[name].[ext]" }, + { test: /\.ipynb$/, loader: 'json-loader' } + ], + }, + mode: 'development' +} diff --git a/packages/base-manager/tsconfig.json b/packages/base-manager/tsconfig.json new file mode 100644 index 0000000000..fb9b75644f --- /dev/null +++ b/packages/base-manager/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfigbase", + "compilerOptions": { + "outDir": "lib", + "rootDir": "src", + "lib": ["dom", "es5", "es2015.promise", "es2015.iterable"], + "target": "es5" + }, + "include": ["src/*"], + "references": [ + { + "path": "../base" + } + ] +} diff --git a/packages/base/src/index.ts b/packages/base/src/index.ts index b74cedbbae..f0dba4abaa 100644 --- a/packages/base/src/index.ts +++ b/packages/base/src/index.ts @@ -1,7 +1,6 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -export * from './manager-base'; export * from './widget'; export * from './widget_layout'; export * from './widget_style'; diff --git a/packages/base/src/utils.ts b/packages/base/src/utils.ts index 2d5422ad79..e66c092d17 100644 --- a/packages/base/src/utils.ts +++ b/packages/base/src/utils.ts @@ -1,10 +1,6 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import { - toByteArray, fromByteArray -} from 'base64-js'; - import { JSONObject, JSONValue, UUID, JSONExt } from '@lumino/coreutils'; @@ -241,63 +237,3 @@ function remove_buffers(state: BufferJSON | ISerializeable): ISerializedState { const new_state = remove(state, []) as JSONObject; return {state: new_state, buffers: buffers, buffer_paths: buffer_paths}; } - -const hexTable = [ - '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0A', '0B', '0C', '0D', '0E', '0F', - '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1A', '1B', '1C', '1D', '1E', '1F', - '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2A', '2B', '2C', '2D', '2E', '2F', - '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3A', '3B', '3C', '3D', '3E', '3F', - '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4A', '4B', '4C', '4D', '4E', '4F', - '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5A', '5B', '5C', '5D', '5E', '5F', - '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6A', '6B', '6C', '6D', '6E', '6F', - '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7A', '7B', '7C', '7D', '7E', '7F', - '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8A', '8B', '8C', '8D', '8E', '8F', - '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9A', '9B', '9C', '9D', '9E', '9F', - 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9', 'AA', 'AB', 'AC', 'AD', 'AE', 'AF', - 'B0', 'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9', 'BA', 'BB', 'BC', 'BD', 'BE', 'BF', - 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9', 'CA', 'CB', 'CC', 'CD', 'CE', 'CF', - 'D0', 'D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9', 'DA', 'DB', 'DC', 'DD', 'DE', 'DF', - 'E0', 'E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9', 'EA', 'EB', 'EC', 'ED', 'EE', 'EF', - 'F0', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'FA', 'FB', 'FC', 'FD', 'FE', 'FF' -]; - -/** - * Convert an ArrayBuffer to a hex string. - */ -export -function bufferToHex(buffer: ArrayBuffer): string { - const x = new Uint8Array(buffer); - const s = []; - for (let i = 0; i < x.length; i++) { - s.push(hexTable[x[i]]); - } - return s.join(''); -} - -/** - * Convert a hex string to an ArrayBuffer. - */ -export -function hexToBuffer(hex: string): ArrayBuffer { - const x = new Uint8Array(hex.length / 2); - for (let i = 0; i < hex.length; i += 2) { - x[i / 2] = parseInt(hex.slice(i, i + 2), 16); - } - return x.buffer; -} - -/** - * Convert an ArrayBuffer to a base64 string. - */ -export -function bufferToBase64(buffer: ArrayBuffer): string { - return fromByteArray(new Uint8Array(buffer)); -} - -/** - * Convert a base64 string to an ArrayBuffer. - */ -export -function base64ToBuffer(base64: string): ArrayBuffer { - return toByteArray(base64).buffer; -} diff --git a/packages/base/src/widget.ts b/packages/base/src/widget.ts index e05c1ec4c1..b7ed3d3c48 100644 --- a/packages/base/src/widget.ts +++ b/packages/base/src/widget.ts @@ -1,7 +1,6 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import * as managerBase from './manager-base'; import * as utils from './utils'; import * as backbonePatch from './backbone-patch'; @@ -48,6 +47,122 @@ import { KernelMessage } from '@jupyterlab/services'; +/** + * The options for a model. + * + * #### Notes + * Either a comm or a model_id must be provided. + */ +export +interface IModelOptions { + + /** + * Target name of the widget model to create. + */ + model_name: string; + + /** + * Module name of the widget model to create. + */ + model_module: string; + + /** + * Semver version requirement for the model module. + */ + model_module_version: string; + + /** + * Target name of the widget view to create. + */ + view_name?: string | null; + + /** + * Module name of the widget view to create. + */ + view_module?: string | null; + + /** + * Semver version requirement for the view module. + */ + view_module_version?: string; + + /** + * Comm object associated with the widget. + */ + comm?: any; + + /** + * The model id to use. If not provided, the comm id of the comm is used. + */ + model_id?: string; +} + +/** + * The options for a connected model. + * + * This gives all of the information needed to instantiate a comm to a new + * widget on the kernel side (so view information is mandatory). + * + * #### Notes + * Either a comm or a model_id must be provided. + */ +export +interface IWidgetOptions extends IModelOptions { + /** + * Target name of the widget model to create. + */ + model_name: string; + + /** + * Module name of the widget model to create. + */ + model_module: string; + + /** + * Semver version requirement for the model module. + */ + model_module_version: string; + + /** + * Target name of the widget view to create. + */ + view_name: string | null; + + /** + * Module name of the widget view to create. + */ + view_module: string | null; + + /** + * Semver version requirement for the view module. + */ + view_module_version: string; + + /** + * Comm object associated with the widget. + */ + comm?: IClassicComm; + + /** + * The model id to use. If not provided, the comm id of the comm is used. + */ + model_id?: string; +} + +/** + * + */ +export +interface IWidgetManager { + get_model(model_id: string): Promise | undefined; + new_widget(options: IWidgetOptions, serialized_state?: JSONObject): Promise; + new_model(options: IModelOptions, serialized_state?: any): Promise; + register_model(model_id: string, modelPromise: Promise): void; + create_view(model: DOMWidgetModel, options?: any): Promise; + create_view(model: WidgetModel, options?: any): Promise; + callbacks(view?: WidgetView): ICallbacks; +} + /** * Replace model ids with models recursively. @@ -55,7 +170,7 @@ import { export function unpack_models( value: any | Dict | string | (Dict | string)[], - manager: managerBase.ManagerBase + manager: IWidgetManager ): Promise | WidgetModel[] | any> { if (Array.isArray(value)) { const unpacked: any[] = []; @@ -84,7 +199,7 @@ function unpack_models( export interface ISerializers { [key: string]: { - deserialize?: (value?: any, manager?: managerBase.ManagerBase) => any; + deserialize?: (value?: any, manager?: IWidgetManager) => any; serialize?: (value?: any, widget?: WidgetModel) => any; }; } @@ -544,7 +659,7 @@ class WidgetModel extends Backbone.Model { * is an instance of widget manager, which is required for the * deserialization of widget models. */ - static _deserialize_state(state: JSONObject, manager: managerBase.ManagerBase): Promise> { + static _deserialize_state(state: JSONObject, manager: IWidgetManager): Promise> { const serializers = this.serializers; let deserialized: Dict; if (serializers) { @@ -569,7 +684,7 @@ class WidgetModel extends Backbone.Model { // constructor. We initialize the default values above in the initialization // function so that they are ready for the user code, and to not override // values subclasses may set in their initialization functions. - widget_manager: managerBase.ManagerBase; + widget_manager: IWidgetManager; model_id: string; views: {[key: string]: Promise}; state_change: Promise; diff --git a/packages/base/test/src/dummy-manager.ts b/packages/base/test/src/dummy-manager.ts index e3c4d12a44..d76d30cd0f 100644 --- a/packages/base/test/src/dummy-manager.ts +++ b/packages/base/test/src/dummy-manager.ts @@ -2,12 +2,15 @@ // Distributed under the terms of the Modified BSD License. import * as widgets from '../../lib'; -import * as services from '@jupyterlab/services'; import * as Backbone from 'backbone'; import * as sinon from 'sinon'; void sinon; +import { + JSONObject +} from '@lumino/coreutils'; + let numComms = 0; export @@ -132,22 +135,11 @@ class BinaryWidgetView extends TestWidgetView { const testWidgets = {TestWidget, TestWidgetView, BinaryWidget, BinaryWidgetView}; export -class DummyManager extends widgets.ManagerBase { +class DummyManager implements widgets.IWidgetManager { constructor() { - super(); this.el = window.document.createElement('div'); } - display_view(msg: services.KernelMessage.IMessage, view: Backbone.View, options: any): Promise { - // TODO: make this a spy - // TODO: return an html element - return Promise.resolve(view).then(view => { - this.el.appendChild(view.el); - view.on('remove', () => console.log('view removed', view)); - return view.el; - }); - } - protected loadClass(className: string, moduleName: string, moduleVersion: string): Promise { if (moduleName === '@jupyter-widgets/base') { if ((widgets as any)[className]) { @@ -166,13 +158,136 @@ class DummyManager extends widgets.ManagerBase { } } - _get_comm_info(): Promise<{}> { - return Promise.resolve({}); + el: HTMLElement; + + + /** + * Creates a promise for a view of a given model + * + * Make sure the view creation is not out of order with + * any state updates. + */ + create_view(model: widgets.DOMWidgetModel, options?: any): Promise + create_view(model: widgets.WidgetModel, options?: any): Promise { + throw new Error('Not implemented in dummy manager'); } - _create_comm(): Promise { - return Promise.resolve(new MockComm()); + /** + * callback handlers specific to a view + */ + callbacks(view?: widgets.WidgetView): widgets.ICallbacks { + return {}; } - el: HTMLElement; + /** + * Get a promise for a model by model id. + * + * #### Notes + * If a model is not found, undefined is returned (NOT a promise). However, + * the calling code should also deal with the case where a rejected promise + * is returned, and should treat that also as a model not found. + */ + get_model(model_id: string): Promise | undefined { + // TODO: Perhaps we should return a Promise.reject if the model is not + // found. Right now this isn't a true async function because it doesn't + // always return a promise. + return this._models[model_id]; + } + + /** + * Create a comm and new widget model. + * @param options - same options as new_model but comm is not + * required and additional options are available. + * @param serialized_state - serialized model attributes. + */ + new_widget(options: widgets.IWidgetOptions, serialized_state: JSONObject = {}): Promise { + return this.new_model(options, serialized_state); + } + + register_model(model_id: string, modelPromise: Promise): void { + this._models[model_id] = modelPromise; + modelPromise.then(model => { + model.once('comm:close', () => { + delete this._models[model_id]; + }); + }); + } + + /** + * Create and return a promise for a new widget model + * + * @param options - the options for creating the model. + * @param serialized_state - attribute values for the model. + * + * @example + * widget_manager.new_model({ + * model_name: 'IntSlider', + * model_module: '@jupyter-widgets/controls', + * model_module_version: '1.0.0', + * model_id: 'u-u-i-d' + * }).then((model) => { console.log('Create success!', model); }, + * (err) => {console.error(err)}); + * + */ + async new_model(options: widgets.IModelOptions, serialized_state: any = {}): Promise { + let model_id; + if (options.model_id) { + model_id = options.model_id; + } else if (options.comm) { + model_id = options.model_id = options.comm.comm_id; + } else { + throw new Error('Neither comm nor model_id provided in options object. At least one must exist.'); + } + + const modelPromise = this._make_model(options, serialized_state); + // this call needs to happen before the first `await`, see note in `set_state`: + this.register_model(model_id, modelPromise); + return await modelPromise; + } + + async _make_model(options: any, serialized_state: any = {}): Promise { + const model_id = options.model_id; + const model_promise = this.loadClass( + options.model_name, + options.model_module, + options.model_module_version + ); + let ModelType; + try { + ModelType = await model_promise; + } catch (error) { + console.error('Could not instantiate widget'); + throw error; + } + + if (!ModelType) { + throw new Error(`Cannot find model module ${options.model_module}@${options.model_module_version}, ${options.model_name}`); + } + + const attributes = await ModelType._deserialize_state(serialized_state, this); + const modelOptions = { + widget_manager: this, + model_id: model_id, + comm: options.comm, + }; + const widget_model = new ModelType(attributes, modelOptions); + widget_model.name = options.model_name; + widget_model.module = options.model_module; + return widget_model; + + } + + /** + * Resolve a URL relative to the current notebook location. + * + * The default implementation just returns the original url. + */ + resolveUrl(url: string): Promise { + return Promise.resolve(url); + } + + /** + * Dictionary of model ids and model instance promises + */ + private _models: {[key: string]: Promise} = Object.create(null); } diff --git a/packages/base/test/src/index.ts b/packages/base/test/src/index.ts index 519891b25e..01d16dbcde 100644 --- a/packages/base/test/src/index.ts +++ b/packages/base/test/src/index.ts @@ -1,6 +1,5 @@ // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. -import './manager_test'; import './widget_test'; import './utils_test'; diff --git a/packages/controls/test/src/dummy-manager.ts b/packages/controls/test/src/dummy-manager.ts index a85ebbb28d..547574adb5 100644 --- a/packages/controls/test/src/dummy-manager.ts +++ b/packages/controls/test/src/dummy-manager.ts @@ -6,6 +6,7 @@ import * as services from '@jupyterlab/services'; import * as Backbone from 'backbone'; import * as base from '@jupyter-widgets/base'; import { WidgetModel, WidgetView } from '@jupyter-widgets/base'; +import { ManagerBase } from '@jupyter-widgets/base-manager'; let numComms = 0; @@ -83,7 +84,7 @@ class TestWidgetView extends base.WidgetView { const testWidgets = {TestWidget, TestWidgetView}; export -class DummyManager extends base.ManagerBase { +class DummyManager extends ManagerBase { constructor() { super(); this.el = window.document.createElement('div'); diff --git a/packages/html-manager/src/htmlmanager.ts b/packages/html-manager/src/htmlmanager.ts index 4fddd318ac..00a29839c4 100644 --- a/packages/html-manager/src/htmlmanager.ts +++ b/packages/html-manager/src/htmlmanager.ts @@ -4,6 +4,7 @@ import * as widgets from '@jupyter-widgets/controls'; import * as base from '@jupyter-widgets/base'; import * as outputWidgets from './output'; +import { ManagerBase } from '@jupyter-widgets/base-manager'; import * as LuminoWidget from '@lumino/widgets'; import { RenderMimeRegistry, standardRendererFactories } from '@jupyterlab/rendermime'; @@ -12,7 +13,7 @@ import { WidgetRenderer, WIDGET_MIMETYPE } from './output_renderers'; import { WidgetModel, WidgetView } from '@jupyter-widgets/base'; export -class HTMLManager extends base.ManagerBase { +class HTMLManager extends ManagerBase { constructor(options?: {loader?: (moduleName: string, moduleVersion: string) => Promise}) { super(); diff --git a/packages/jupyterlab-manager/package.json b/packages/jupyterlab-manager/package.json index dafaa21318..d2426089be 100644 --- a/packages/jupyterlab-manager/package.json +++ b/packages/jupyterlab-manager/package.json @@ -35,6 +35,7 @@ }, "dependencies": { "@jupyter-widgets/base": "^2.0.2", + "@jupyter-widgets/base-manager": "^1.0.0", "@jupyter-widgets/controls": "^1.5.3", "@jupyter-widgets/output": "^2.0.1", "@jupyterlab/application": "^2.0.0-beta.2", diff --git a/packages/jupyterlab-manager/src/manager.ts b/packages/jupyterlab-manager/src/manager.ts index 3903ca47f3..f486222ce0 100644 --- a/packages/jupyterlab-manager/src/manager.ts +++ b/packages/jupyterlab-manager/src/manager.ts @@ -5,10 +5,14 @@ import * as Backbone from 'backbone'; import { - ManagerBase, shims, IClassicComm, IWidgetRegistryData, ExportMap, - ExportData, WidgetModel, WidgetView, put_buffers, serialize_state, IStateOptions, ICallbacks + shims, IClassicComm, IWidgetRegistryData, ExportMap, + ExportData, WidgetModel, WidgetView, put_buffers, ICallbacks } from '@jupyter-widgets/base'; +import { + ManagerBase, serialize_state, IStateOptions +} from '@jupyter-widgets/base-manager'; + import { IDisposable } from '@lumino/disposable'; diff --git a/widgetsnbextension/src/manager.js b/widgetsnbextension/src/manager.js index 876e33b524..ff964f736f 100644 --- a/widgetsnbextension/src/manager.js +++ b/widgetsnbextension/src/manager.js @@ -4,6 +4,7 @@ var _ = require("underscore"); var base = require("@jupyter-widgets/base"); +var ManagerBase = require('@jupyter-widgets/base-manager').ManagerBase; var widgets = require("@jupyter-widgets/controls"); var outputWidgets = require("./widget_output"); var saveState = require("./save_state"); @@ -51,7 +52,7 @@ function new_comm(manager, target_name, data, callbacks, metadata, comm_id, buff //-------------------------------------------------------------------- -export class WidgetManager extends base.ManagerBase { +export class WidgetManager extends ManagerBase { constructor(comm_manager, notebook) { super(); // Managers are stored in *reverse* order, so that _managers[0] is the most recent. @@ -327,7 +328,7 @@ export class WidgetManager extends base.ManagerBase { * Callback handlers for a specific view */ callbacks(view) { - var callbacks = base.ManagerBase.prototype.callbacks.call(this, view); + var callbacks = ManagerBase.prototype.callbacks.call(this, view); if (view && view.options.iopub_callbacks) { callbacks.iopub = view.options.iopub_callbacks } From 78ffe878a973527a696baa1da4ae98913cd1bc35 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Thu, 9 Jan 2020 16:54:09 +0100 Subject: [PATCH 2/5] Integrity fix --- packages/base-manager/package.json | 3 ++- packages/base/package.json | 1 - packages/html-manager/package.json | 1 + packages/jupyterlab-manager/package.json | 2 +- widgetsnbextension/package.json | 1 + 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/base-manager/package.json b/packages/base-manager/package.json index efe5ad62da..8e57207ec2 100644 --- a/packages/base-manager/package.json +++ b/packages/base-manager/package.json @@ -1,6 +1,6 @@ { "name": "@jupyter-widgets/base-manager", - "version": "1.0.0", + "version": "0.1.0", "description": "Jupyter interactive widgets - base manager", "repository": { "type": "git", @@ -32,6 +32,7 @@ "test:unit:ie": "npm run test:unit:default -- --browsers=IE" }, "dependencies": { + "@jupyter-widgets/base": "^2.0.2", "@jupyterlab/services": "^5.0.0-beta.2", "@lumino/coreutils": "^1.2.0", "base64-js": "^1.2.1" diff --git a/packages/base/package.json b/packages/base/package.json index a682dc3267..69d8ed8932 100644 --- a/packages/base/package.json +++ b/packages/base/package.json @@ -39,7 +39,6 @@ "@types/backbone": "^1.4.1", "@types/lodash": "^4.14.134", "backbone": "1.2.3", - "base64-js": "^1.2.1", "jquery": "^3.1.1", "lodash": "^4.17.4" }, diff --git a/packages/html-manager/package.json b/packages/html-manager/package.json index 5d23e71d26..13226ee3c5 100644 --- a/packages/html-manager/package.json +++ b/packages/html-manager/package.json @@ -36,6 +36,7 @@ }, "dependencies": { "@jupyter-widgets/base": "^2.0.2", + "@jupyter-widgets/base-manager": "^0.1.0", "@jupyter-widgets/controls": "^1.5.3", "@jupyter-widgets/output": "^2.0.1", "@jupyter-widgets/schema": "^0.4.0", diff --git a/packages/jupyterlab-manager/package.json b/packages/jupyterlab-manager/package.json index d2426089be..16c1838402 100644 --- a/packages/jupyterlab-manager/package.json +++ b/packages/jupyterlab-manager/package.json @@ -35,7 +35,7 @@ }, "dependencies": { "@jupyter-widgets/base": "^2.0.2", - "@jupyter-widgets/base-manager": "^1.0.0", + "@jupyter-widgets/base-manager": "^0.1.0", "@jupyter-widgets/controls": "^1.5.3", "@jupyter-widgets/output": "^2.0.1", "@jupyterlab/application": "^2.0.0-beta.2", diff --git a/widgetsnbextension/package.json b/widgetsnbextension/package.json index f14c141807..d2208393ed 100644 --- a/widgetsnbextension/package.json +++ b/widgetsnbextension/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@jupyter-widgets/base": "^2.0.2", + "@jupyter-widgets/base-manager": "^0.1.0", "@jupyter-widgets/controls": "^1.5.3", "@jupyter-widgets/html-manager": "^0.18.4", "@jupyter-widgets/output": "^2.0.1", From eff10fc36ef44fdadc6dc355b256f3aab1e7d991 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Thu, 9 Jan 2020 16:59:28 +0100 Subject: [PATCH 3/5] examples --- examples/web1/manager.js | 3 ++- examples/web1/package.json | 1 + examples/web2/manager.js | 3 ++- examples/web2/package.json | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/web1/manager.js b/examples/web1/manager.js index 4212df8c32..0f3c4c249d 100644 --- a/examples/web1/manager.js +++ b/examples/web1/manager.js @@ -1,8 +1,9 @@ var base = require('@jupyter-widgets/base'); +var ManagerBase = require('@jupyter-widgets/base-manager').ManagerBase; var controls = require('@jupyter-widgets/controls'); var LuminoWidget = require('@lumino/widgets').Widget; -class WidgetManager extends base.ManagerBase { +class WidgetManager extends ManagerBase { constructor(el) { super(); this.el = el; diff --git a/examples/web1/package.json b/examples/web1/package.json index 70446d5cd2..c844ba6aab 100644 --- a/examples/web1/package.json +++ b/examples/web1/package.json @@ -17,6 +17,7 @@ }, "dependencies": { "@jupyter-widgets/base": "^2.0.2", + "@jupyter-widgets/base-manager": "^0.1.0", "@jupyter-widgets/controls": "^1.5.3" }, "devDependencies": { diff --git a/examples/web2/manager.js b/examples/web2/manager.js index 4212df8c32..0f3c4c249d 100644 --- a/examples/web2/manager.js +++ b/examples/web2/manager.js @@ -1,8 +1,9 @@ var base = require('@jupyter-widgets/base'); +var ManagerBase = require('@jupyter-widgets/base-manager').ManagerBase; var controls = require('@jupyter-widgets/controls'); var LuminoWidget = require('@lumino/widgets').Widget; -class WidgetManager extends base.ManagerBase { +class WidgetManager extends ManagerBase { constructor(el) { super(); this.el = el; diff --git a/examples/web2/package.json b/examples/web2/package.json index 71e3d218cb..cf5f6d0821 100644 --- a/examples/web2/package.json +++ b/examples/web2/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@jupyter-widgets/base": "^2.0.2", + "@jupyter-widgets/base-manager": "^0.1.0", "@jupyter-widgets/controls": "^1.5.3", "codemirror": "^5.48.0", "font-awesome": "^4.7.0" From 36bae01e5e49a29f6270152e3b584dad102ec4e6 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Fri, 10 Jan 2020 09:52:31 +0100 Subject: [PATCH 4/5] Refactor some typings --- packages/base/src/index.ts | 1 + packages/base/src/manager.ts | 187 +++++++++++++++++++++++++++++++++++ packages/base/src/widget.ts | 121 +---------------------- 3 files changed, 192 insertions(+), 117 deletions(-) create mode 100644 packages/base/src/manager.ts diff --git a/packages/base/src/index.ts b/packages/base/src/index.ts index f0dba4abaa..4be2d65044 100644 --- a/packages/base/src/index.ts +++ b/packages/base/src/index.ts @@ -2,6 +2,7 @@ // Distributed under the terms of the Modified BSD License. export * from './widget'; +export * from './manager'; export * from './widget_layout'; export * from './widget_style'; export * from './services-shim'; diff --git a/packages/base/src/manager.ts b/packages/base/src/manager.ts new file mode 100644 index 0000000000..0745c07cb5 --- /dev/null +++ b/packages/base/src/manager.ts @@ -0,0 +1,187 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { + JSONObject +} from '@lumino/coreutils'; + +import { + IClassicComm, ICallbacks +} from './services-shim'; + +import { + DOMWidgetModel, DOMWidgetView, WidgetModel, WidgetView +} from './widget'; + + +/** + * The options for a model. + * + * #### Notes + * Either a comm or a model_id must be provided. + */ +export +interface IModelOptions { + + /** + * Target name of the widget model to create. + */ + model_name: string; + + /** + * Module name of the widget model to create. + */ + model_module: string; + + /** + * Semver version requirement for the model module. + */ + model_module_version: string; + + /** + * Target name of the widget view to create. + */ + view_name?: string | null; + + /** + * Module name of the widget view to create. + */ + view_module?: string | null; + + /** + * Semver version requirement for the view module. + */ + view_module_version?: string; + + /** + * Comm object associated with the widget. + */ + comm?: any; + + /** + * The model id to use. If not provided, the comm id of the comm is used. + */ + model_id?: string; +} + +/** + * The options for a connected model. + * + * This gives all of the information needed to instantiate a comm to a new + * widget on the kernel side (so view information is mandatory). + * + * #### Notes + * Either a comm or a model_id must be provided. + */ +export +interface IWidgetOptions extends IModelOptions { + /** + * Target name of the widget model to create. + */ + model_name: string; + + /** + * Module name of the widget model to create. + */ + model_module: string; + + /** + * Semver version requirement for the model module. + */ + model_module_version: string; + + /** + * Target name of the widget view to create. + */ + view_name: string | null; + + /** + * Module name of the widget view to create. + */ + view_module: string | null; + + /** + * Semver version requirement for the view module. + */ + view_module_version: string; + + /** + * Comm object associated with the widget. + */ + comm?: IClassicComm; + + /** + * The model id to use. If not provided, the comm id of the comm is used. + */ + model_id?: string; +} + +/** + * The widget manager interface exposed on the Widget instances + */ +export +interface IWidgetManager { + /** + * Get a promise for a model by model id. + * + * #### Notes + * If a model is not found, undefined is returned (NOT a promise). However, + * the calling code should also deal with the case where a rejected promise + * is returned, and should treat that also as a model not found. + */ + get_model(model_id: string): Promise | undefined; + + /** + * Register a model instance promise with the manager. + * + * By registering the model, it can later be retrieved with `get_model`. + */ + register_model(model_id: string, modelPromise: Promise): void; + + /** + * Create a comm and new widget model. + * @param options - same options as new_model but comm is not + * required and additional options are available. + * @param serialized_state - serialized model attributes. + */ + new_widget(options: IWidgetOptions, serialized_state?: JSONObject): Promise; + + /** + * Create and return a promise for a new widget model + * + * @param options - the options for creating the model. + * @param serialized_state - attribute values for the model. + * + * @example + * widget_manager.new_model({ + * model_name: 'IntSlider', + * model_module: '@jupyter-widgets/controls', + * model_module_version: '1.0.0', + * model_id: 'u-u-i-d' + * }).then((model) => { console.log('Create success!', model); }, + * (err) => {console.error(err)}); + * + */ + new_model(options: IModelOptions, serialized_state?: JSONObject): Promise; + + /** + * Creates a promise for a view of a given model + * + * Make sure the view creation is not out of order with + * any state updates. + */ + create_view(model: DOMWidgetModel, options?: unknown): Promise; + create_view(model: WidgetModel, options?: unknown): Promise; + + /** + * callback handlers specific to a view + */ + callbacks(view?: WidgetView): ICallbacks; + + /** + * Resolve a URL relative to the current notebook location. + * + * The default implementation just returns the original url. + */ + resolveUrl(url: string): Promise; +} diff --git a/packages/base/src/widget.ts b/packages/base/src/widget.ts index b7ed3d3c48..3ddbc6b321 100644 --- a/packages/base/src/widget.ts +++ b/packages/base/src/widget.ts @@ -31,6 +31,10 @@ import { StyleModel } from './widget_style'; +import { + IWidgetManager +} from './manager'; + import { IClassicComm, ICallbacks } from './services-shim'; @@ -47,123 +51,6 @@ import { KernelMessage } from '@jupyterlab/services'; -/** - * The options for a model. - * - * #### Notes - * Either a comm or a model_id must be provided. - */ -export -interface IModelOptions { - - /** - * Target name of the widget model to create. - */ - model_name: string; - - /** - * Module name of the widget model to create. - */ - model_module: string; - - /** - * Semver version requirement for the model module. - */ - model_module_version: string; - - /** - * Target name of the widget view to create. - */ - view_name?: string | null; - - /** - * Module name of the widget view to create. - */ - view_module?: string | null; - - /** - * Semver version requirement for the view module. - */ - view_module_version?: string; - - /** - * Comm object associated with the widget. - */ - comm?: any; - - /** - * The model id to use. If not provided, the comm id of the comm is used. - */ - model_id?: string; -} - -/** - * The options for a connected model. - * - * This gives all of the information needed to instantiate a comm to a new - * widget on the kernel side (so view information is mandatory). - * - * #### Notes - * Either a comm or a model_id must be provided. - */ -export -interface IWidgetOptions extends IModelOptions { - /** - * Target name of the widget model to create. - */ - model_name: string; - - /** - * Module name of the widget model to create. - */ - model_module: string; - - /** - * Semver version requirement for the model module. - */ - model_module_version: string; - - /** - * Target name of the widget view to create. - */ - view_name: string | null; - - /** - * Module name of the widget view to create. - */ - view_module: string | null; - - /** - * Semver version requirement for the view module. - */ - view_module_version: string; - - /** - * Comm object associated with the widget. - */ - comm?: IClassicComm; - - /** - * The model id to use. If not provided, the comm id of the comm is used. - */ - model_id?: string; -} - -/** - * - */ -export -interface IWidgetManager { - get_model(model_id: string): Promise | undefined; - new_widget(options: IWidgetOptions, serialized_state?: JSONObject): Promise; - new_model(options: IModelOptions, serialized_state?: any): Promise; - register_model(model_id: string, modelPromise: Promise): void; - create_view(model: DOMWidgetModel, options?: any): Promise; - create_view(model: WidgetModel, options?: any): Promise; - callbacks(view?: WidgetView): ICallbacks; -} - - /** * Replace model ids with models recursively. */ From cf5be0cf2f271045c9ac6bba4c768ddd12a00792 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Fri, 10 Jan 2020 10:32:05 +0100 Subject: [PATCH 5/5] Fix dev-install --- dev-install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-install.sh b/dev-install.sh index ce6c2759f1..bccba2de3e 100755 --- a/dev-install.sh +++ b/dev-install.sh @@ -53,6 +53,7 @@ pip install -v -e . if test "$skip_jupyter_lab" != yes; then jupyter labextension link ./packages/base --no-build + jupyter labextension link ./packages/base-manager --no-build jupyter labextension link ./packages/controls --no-build jupyter labextension link ./packages/output --no-build jupyter labextension install ./packages/jupyterlab-manager