// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import * as libembed from './libembed'; /** * Load a package using requirejs and return a promise * * @param pkg Package name or names to load */ const requirePromise = function (pkg: string | string[]): Promise { return new Promise((resolve, reject) => { const require = (window as any).requirejs; if (require === undefined) { reject('Requirejs is needed, please ensure it is loaded on the page.'); } else { require(pkg, resolve, reject); } }); }; let cdn = 'https://cdn.jsdelivr.net/npm/'; let onlyCDN = false; // find the data-cdn for any script tag, assuming it is only used for embed-amd.js const scripts = document.getElementsByTagName('script'); Array.prototype.forEach.call(scripts, (script: HTMLScriptElement) => { cdn = script.getAttribute('data-jupyter-widgets-cdn') || cdn; onlyCDN = onlyCDN || script.hasAttribute('data-jupyter-widgets-cdn-only'); }); function moduleNameToCDNUrl(moduleName: string, moduleVersion: string): string { let packageName = moduleName; let fileName = 'index'; // default filename // if a '/' is present, like 'foo/bar', packageName is changed to 'foo', and path to 'bar' // We first find the first '/' let index = moduleName.indexOf('/'); if (index != -1 && moduleName[0] == '@') { // if we have a namespace, it's a different story // @foo/bar/baz should translate to @foo/bar and baz // so we find the 2nd '/' index = moduleName.indexOf('/', index + 1); } if (index != -1) { fileName = moduleName.substr(index + 1); packageName = moduleName.substr(0, index); } return `${cdn}${packageName}@${moduleVersion}/dist/${fileName}`; } /** * Load an amd module locally and fall back to specified CDN if unavailable. * * @param moduleName The name of the module to load.. * @param version The semver range for the module, if loaded from a CDN. * * By default, the CDN service used is jsDelivr. However, this default can be * overridden by specifying another URL via the HTML attribute * "data-jupyter-widgets-cdn" on a script tag of the page. * * The semver range is only used with the CDN. */ export function requireLoader( moduleName: string, moduleVersion: string ): Promise { const require = (window as any).requirejs; if (require === undefined) { throw new Error( 'Requirejs is needed, please ensure it is loaded on the page.' ); } function loadFromCDN(): Promise { const conf: { paths: { [key: string]: string } } = { paths: {} }; conf.paths[moduleName] = moduleNameToCDNUrl(moduleName, moduleVersion); require.config(conf); return requirePromise([`${moduleName}`]); } if (onlyCDN) { console.log(`Loading from ${cdn} for ${moduleName}@${moduleVersion}`); return loadFromCDN(); } return requirePromise([`${moduleName}`]).catch((err) => { const failedId = err.requireModules && err.requireModules[0]; if (failedId) { require.undef(failedId); console.log(`Falling back to ${cdn} for ${moduleName}@${moduleVersion}`); return loadFromCDN(); } }); } /** * Render widgets in a given element. * * @param element (default document.documentElement) The element containing widget state and views. * @param loader (default requireLoader) The function used to look up the modules containing * the widgets' models and views classes. (The default loader looks them up on jsDelivr) */ export function renderWidgets( element = document.documentElement, loader: ( moduleName: string, moduleVersion: string ) => Promise = requireLoader ): void { requirePromise(['@jupyter-widgets/html-manager']).then((htmlmanager) => { const managerFactory = (): any => { return new htmlmanager.HTMLManager({ loader: loader }); }; libembed.renderWidgets(managerFactory, element); }); }