diff --git a/dev_mode/index.js b/dev_mode/index.js index abc0058763cf..17e12088e781 100644 --- a/dev_mode/index.js +++ b/dev_mode/index.js @@ -21,89 +21,38 @@ require('./imports.css'); */ function main() { var JupyterLab = require('@jupyterlab/application').JupyterLab; - - // Get the disabled extensions. - var disabled = { patterns: [], matches: [] }; - var disabledExtensions = []; - try { - var tempDisabled = PageConfig.getOption('disabledExtensions'); - if (tempDisabled) { - disabledExtensions = JSON.parse(tempDisabled).map(function(pattern) { - disabled.patterns.push(pattern); - return { raw: pattern, rule: new RegExp(pattern) }; - }); - } - } catch (error) { - console.warn('Unable to parse disabled extensions.', error); - } - - // Get the deferred extensions. - var deferred = { patterns: [], matches: [] }; - var deferredExtensions = []; + var disabled = []; + var deferred = []; var ignorePlugins = []; - try { - var tempDeferred = PageConfig.getOption('deferredExtensions'); - if (tempDeferred) { - deferredExtensions = JSON.parse(tempDeferred).map(function(pattern) { - deferred.patterns.push(pattern); - return { raw: pattern, rule: new RegExp(pattern) }; - }); - } - } catch (error) { - console.warn('Unable to parse deferred extensions.', error); - } - - function isDeferred(value) { - return deferredExtensions.some(function(pattern) { - return pattern.raw === value || pattern.rule.test(value); - }); - } - - function isDisabled(value) { - return disabledExtensions.some(function(pattern) { - return pattern.raw === value || pattern.rule.test(value); - }); - } - var register = []; // Handle the registered mime extensions. var mimeExtensions = []; var extension; var extMod; + var plugins = []; {{#each jupyterlab_mime_extensions}} try { - if (isDeferred('{{key}}')) { - deferred.matches.push('{{key}}'); - ignorePlugins.push('{{key}}'); + extMod = require('{{@key}}/{{this}}'); + extension = extMod.default; + + // Handle CommonJS exports. + if (!extMod.hasOwnProperty('__esModule')) { + extension = extMod; } - if (isDisabled('{{@key}}')) { - disabled.matches.push('{{@key}}'); - } else { - extMod = require('{{@key}}/{{this}}'); - extension = extMod.default; - - // Handle CommonJS exports. - if (!extMod.hasOwnProperty('__esModule')) { - extension = extMod; - } - if (Array.isArray(extension)) { - extension.forEach(function(plugin) { - if (isDeferred(plugin.id)) { - deferred.matches.push(plugin.id); - ignorePlugins.push(plugin.id); - } - if (isDisabled(plugin.id)) { - disabled.matches.push(plugin.id); - return; - } - mimeExtensions.push(plugin); - }); - } else { - mimeExtensions.push(extension); + plugins = Array.isArray(extension) ? extension : [extension]; + plugins.forEach(function(plugin) { + if (PageConfig.Extension.isDeferred(plugin.id)) { + deferred.push(plugin.id); + ignorePlugins.push(plugin.id); } - } + if (PageConfig.Extension.isDisabled(plugin.id)) { + disabled.push(plugin.id); + return; + } + mimeExtensions.push(plugin); + }); } catch (e) { console.error(e); } @@ -112,46 +61,42 @@ function main() { // Handled the registered standard extensions. {{#each jupyterlab_extensions}} try { - if (isDeferred('{{key}}')) { - deferred.matches.push('{{key}}'); - ignorePlugins.push('{{key}}'); + extMod = require('{{@key}}/{{this}}'); + extension = extMod.default; + + // Handle CommonJS exports. + if (!extMod.hasOwnProperty('__esModule')) { + extension = extMod; } - if (isDisabled('{{@key}}')) { - disabled.matches.push('{{@key}}'); - } else { - extMod = require('{{@key}}/{{this}}'); - extension = extMod.default; - - // Handle CommonJS exports. - if (!extMod.hasOwnProperty('__esModule')) { - extension = extMod; - } - if (Array.isArray(extension)) { - extension.forEach(function(plugin) { - if (isDeferred(plugin.id)) { - deferred.matches.push(plugin.id); - ignorePlugins.push(plugin.id); - } - if (isDisabled(plugin.id)) { - disabled.matches.push(plugin.id); - return; - } - register.push(plugin); - }); - } else { - register.push(extension); + plugins = Array.isArray(extension) ? extension : [extension]; + plugins.forEach(function(plugin) { + if (PageConfig.Extension.isDeferred(plugin.id)) { + deferred.push(plugin.id); + ignorePlugins.push(plugin.id); } - } + if (PageConfig.Extension.isDisabled(plugin.id)) { + disabled.push(plugin.id); + return; + } + register.push(plugin); + }); } catch (e) { console.error(e); } {{/each}} - var lab = new JupyterLab({ mimeExtensions: mimeExtensions, - disabled: disabled, - deferred: deferred + disabled: { + matches: disabled, + patterns: PageConfig.Extension.disabled + .map(function (val) { return val.raw; }) + }, + deferred: { + matches: deferred, + patterns: PageConfig.Extension.deferred + .map(function (val) { return val.raw; }) + }, }); register.forEach(function(item) { lab.registerPluginModule(item); }); lab.start({ ignorePlugins: ignorePlugins }); diff --git a/jupyterlab/staging/index.js b/jupyterlab/staging/index.js index bd0596206fdd..dd5f15cb2cfb 100644 --- a/jupyterlab/staging/index.js +++ b/jupyterlab/staging/index.js @@ -22,89 +22,38 @@ require('./imports.css'); */ function main() { var JupyterLab = require('@jupyterlab/application').JupyterLab; - - // Get the disabled extensions. - var disabled = { patterns: [], matches: [] }; - var disabledExtensions = []; - try { - var tempDisabled = PageConfig.getOption('disabledExtensions'); - if (tempDisabled) { - disabledExtensions = JSON.parse(tempDisabled).map(function(pattern) { - disabled.patterns.push(pattern); - return { raw: pattern, rule: new RegExp(pattern) }; - }); - } - } catch (error) { - console.warn('Unable to parse disabled extensions.', error); - } - - // Get the deferred extensions. - var deferred = { patterns: [], matches: [] }; - var deferredExtensions = []; + var disabled = []; + var deferred = []; var ignorePlugins = []; - try { - var tempDeferred = PageConfig.getOption('deferredExtensions'); - if (tempDeferred) { - deferredExtensions = JSON.parse(tempDeferred).map(function(pattern) { - deferred.patterns.push(pattern); - return { raw: pattern, rule: new RegExp(pattern) }; - }); - } - } catch (error) { - console.warn('Unable to parse deferred extensions.', error); - } - - function isDeferred(value) { - return deferredExtensions.some(function(pattern) { - return pattern.raw === value || pattern.rule.test(value); - }); - } - - function isDisabled(value) { - return disabledExtensions.some(function(pattern) { - return pattern.raw === value || pattern.rule.test(value); - }); - } - var register = []; // Handle the registered mime extensions. var mimeExtensions = []; var extension; var extMod; + var plugins = []; {{#each jupyterlab_mime_extensions}} try { - if (isDeferred('{{key}}')) { - deferred.matches.push('{{key}}'); - ignorePlugins.push('{{key}}'); + extMod = require('{{@key}}/{{this}}'); + extension = extMod.default; + + // Handle CommonJS exports. + if (!extMod.hasOwnProperty('__esModule')) { + extension = extMod; } - if (isDisabled('{{@key}}')) { - disabled.matches.push('{{@key}}'); - } else { - extMod = require('{{@key}}/{{this}}'); - extension = extMod.default; - - // Handle CommonJS exports. - if (!extMod.hasOwnProperty('__esModule')) { - extension = extMod; - } - if (Array.isArray(extension)) { - extension.forEach(function(plugin) { - if (isDeferred(plugin.id)) { - deferred.matches.push(plugin.id); - ignorePlugins.push(plugin.id); - } - if (isDisabled(plugin.id)) { - disabled.matches.push(plugin.id); - return; - } - mimeExtensions.push(plugin); - }); - } else { - mimeExtensions.push(extension); + plugins = Array.isArray(extension) ? extension : [extension]; + plugins.forEach(function(plugin) { + if (PageConfig.Extension.isDeferred(plugin.id)) { + deferred.push(plugin.id); + ignorePlugins.push(plugin.id); } - } + if (PageConfig.Extension.isDisabled(plugin.id)) { + disabled.push(plugin.id); + return; + } + mimeExtensions.push(plugin); + }); } catch (e) { console.error(e); } @@ -113,46 +62,42 @@ function main() { // Handled the registered standard extensions. {{#each jupyterlab_extensions}} try { - if (isDeferred('{{key}}')) { - deferred.matches.push('{{key}}'); - ignorePlugins.push('{{key}}'); + extMod = require('{{@key}}/{{this}}'); + extension = extMod.default; + + // Handle CommonJS exports. + if (!extMod.hasOwnProperty('__esModule')) { + extension = extMod; } - if (isDisabled('{{@key}}')) { - disabled.matches.push('{{@key}}'); - } else { - extMod = require('{{@key}}/{{this}}'); - extension = extMod.default; - - // Handle CommonJS exports. - if (!extMod.hasOwnProperty('__esModule')) { - extension = extMod; - } - if (Array.isArray(extension)) { - extension.forEach(function(plugin) { - if (isDeferred(plugin.id)) { - deferred.matches.push(plugin.id); - ignorePlugins.push(plugin.id); - } - if (isDisabled(plugin.id)) { - disabled.matches.push(plugin.id); - return; - } - register.push(plugin); - }); - } else { - register.push(extension); + plugins = Array.isArray(extension) ? extension : [extension]; + plugins.forEach(function(plugin) { + if (PageConfig.Extension.isDeferred(plugin.id)) { + deferred.push(plugin.id); + ignorePlugins.push(plugin.id); } - } + if (PageConfig.Extension.isDisabled(plugin.id)) { + disabled.push(plugin.id); + return; + } + register.push(plugin); + }); } catch (e) { console.error(e); } {{/each}} - var lab = new JupyterLab({ mimeExtensions: mimeExtensions, - disabled: disabled, - deferred: deferred + disabled: { + matches: disabled, + patterns: PageConfig.Extension.disabled + .map(function (val) { return val.raw; }) + }, + deferred: { + matches: deferred, + patterns: PageConfig.Extension.deferred + .map(function (val) { return val.raw; }) + }, }); register.forEach(function(item) { lab.registerPluginModule(item); }); lab.start({ ignorePlugins: ignorePlugins }); diff --git a/packages/apputils-extension/src/index.ts b/packages/apputils-extension/src/index.ts index 91fead4e32df..24f886e6c083 100644 --- a/packages/apputils-extension/src/index.ts +++ b/packages/apputils-extension/src/index.ts @@ -21,9 +21,9 @@ import { import { Debouncer, - IRateLimiter, ISettingRegistry, IStateDB, + PageConfig, SettingRegistry, StateDB, Throttler, @@ -38,6 +38,8 @@ import { DisposableDelegate } from '@lumino/disposable'; import { Palette } from './palette'; +import { SettingConnector } from './settingconnector'; + import { themesPlugin, themesPaletteMenuPlugin } from './themeplugins'; /** @@ -90,10 +92,40 @@ const paletteRestorer: JupyterFrontEndPlugin = { const settings: JupyterFrontEndPlugin = { id: '@jupyterlab/apputils-extension:settings', activate: async (app: JupyterFrontEnd): Promise => { - const connector = app.serviceManager.settings; - const plugins = (await connector.list()).values; + const { isDisabled } = PageConfig.Extension; + const connector = new SettingConnector(app.serviceManager.settings); + + const registry = new SettingRegistry({ + connector, + plugins: (await connector.list('active')).values + }); + + // If there are plugins that have schemas that are not in the setting + // registry after the application has restored, try to load them manually + // because otherwise, its settings will never become available in the + // setting registry. + void app.restored.then(async () => { + const plugins = await connector.list('all'); + plugins.ids.forEach(async (id, index) => { + if (isDisabled(id) || id in registry.plugins) { + return; + } + + try { + await registry.load(id); + } catch (error) { + console.warn(`Settings failed to load for (${id})`, error); + if (plugins.values[index].schema['jupyter.lab.transform']) { + console.warn( + `This may happen if {autoStart: false} in (${id}) ` + + `or if it is one of the deferredExtensions in page config.` + ); + } + } + }); + }); - return new SettingRegistry({ connector, plugins }); + return registry; }, autoStart: true, provides: ISettingRegistry @@ -190,7 +222,7 @@ const splash: JupyterFrontEndPlugin = { // Create debounced recovery dialog function. let dialog: Dialog; - const recovery: IRateLimiter = new Throttler(async () => { + const recovery = new Throttler(async () => { if (dialog) { return; } diff --git a/packages/apputils-extension/src/settingconnector.ts b/packages/apputils-extension/src/settingconnector.ts new file mode 100644 index 000000000000..b1192c57a799 --- /dev/null +++ b/packages/apputils-extension/src/settingconnector.ts @@ -0,0 +1,44 @@ +import { + DataConnector, + IDataConnector, + ISettingRegistry, + PageConfig +} from '@jupyterlab/coreutils'; + +/** + * A data connector for fetching settings. + * + * #### Notes + * This connector adds a query parameter to the base services setting manager. + */ +export class SettingConnector extends DataConnector< + ISettingRegistry.IPlugin, + string +> { + constructor(connector: IDataConnector) { + super(); + this._connector = connector; + } + + fetch(id: string): Promise { + return this._connector.fetch(id); + } + + async list( + query: 'active' | 'all' = 'all' + ): Promise<{ ids: string[]; values: ISettingRegistry.IPlugin[] }> { + const { isDeferred, isDisabled } = PageConfig.Extension; + let { ids, values } = await this._connector.list(); + + if (query === 'all') { + return { ids, values }; + } + + return { + ids: ids.filter(id => !isDeferred(id) && !isDisabled(id)), + values: values.filter(({ id }) => !isDeferred(id) && !isDisabled(id)) + }; + } + + private _connector: IDataConnector; +} diff --git a/packages/coreutils/src/dataconnector.ts b/packages/coreutils/src/dataconnector.ts index fe24164b182d..637be4960ed1 100644 --- a/packages/coreutils/src/dataconnector.ts +++ b/packages/coreutils/src/dataconnector.ts @@ -16,14 +16,17 @@ import { IDataConnector } from './interfaces'; * ID or filter, but may be set to a different type when an implementation * requires it. Defaults to `string`. * + * @typeparam W - The type of the optional `query` parameter of the `list` + * method. Defaults to `string`. + * * #### Notes * The only abstract method in this class is the `fetch` method, which must be * reimplemented by all subclasses. The `remove` and `save` methods have a * default implementation that returns a promise that will always reject. This * class is a convenience superclass for connectors that only need to `fetch`. */ -export abstract class DataConnector - implements IDataConnector { +export abstract class DataConnector + implements IDataConnector { /** * Retrieve an item from the data connector. * @@ -47,7 +50,7 @@ export abstract class DataConnector * #### Notes * Subclasses should reimplement if they support a back-end that can list. */ - async list(query?: any): Promise<{ ids: V[]; values: T[] }> { + async list(query?: W): Promise<{ ids: V[]; values: T[] }> { throw new Error('DataConnector#list method has not been implemented.'); } diff --git a/packages/coreutils/src/interfaces.ts b/packages/coreutils/src/interfaces.ts index 36df41b2de49..5774e66e6866 100644 --- a/packages/coreutils/src/interfaces.ts +++ b/packages/coreutils/src/interfaces.ts @@ -41,8 +41,11 @@ export interface IChangedArgs { * @typeparam V - The basic token applied to a request, conventionally a string * ID or filter, but may be set to a different type when an implementation * requires it. Defaults to `string`. + * + * @typeparam W - The type of the optional `query` parameter of the `list` + * method. Defaults to `string`; */ -export interface IDataConnector { +export interface IDataConnector { /** * Retrieve an item from the data connector. * @@ -69,7 +72,7 @@ export interface IDataConnector { * retrieving the data. The two lists will always be the same size. If there * is no data, this method will succeed with empty `ids` and `values`. */ - list(query?: any): Promise<{ ids: V[]; values: T[] }>; + list(query?: W): Promise<{ ids: V[]; values: T[] }>; /** * Remove a value using the data connector. diff --git a/packages/coreutils/src/settingregistry.ts b/packages/coreutils/src/settingregistry.ts index 963f2796f180..91ff7ac8aceb 100644 --- a/packages/coreutils/src/settingregistry.ts +++ b/packages/coreutils/src/settingregistry.ts @@ -33,7 +33,7 @@ const copy = JSONExt.deepCopy; * will wait before timing out if it requires a transformation that has not been * registered. */ -const DEFAULT_TRANSFORM_TIMEOUT = 7000; +const DEFAULT_TRANSFORM_TIMEOUT = 1000; /** * The ASCII record separator character. diff --git a/packages/services/src/setting/index.ts b/packages/services/src/setting/index.ts index b1aecf6c6c9d..fac9bb060a53 100644 --- a/packages/services/src/setting/index.ts +++ b/packages/services/src/setting/index.ts @@ -39,6 +39,10 @@ export class SettingManager extends DataConnector< * @returns A promise that resolves if successful. */ async fetch(id: string): Promise { + if (!id) { + throw new Error('Plugin `id` parameter is required for settings fetch.'); + } + const { serverSettings } = this; const { baseUrl, appUrl } = serverSettings; const { makeRequest, ResponseError } = ServerConnection; @@ -46,10 +50,6 @@ export class SettingManager extends DataConnector< const url = Private.url(base, id); const response = await makeRequest(url, {}, serverSettings); - if (!id) { - throw new Error('Plugin `id` parameter is required for settings fetch.'); - } - if (response.status !== 200) { throw new ResponseError(response); } diff --git a/packages/shortcuts-extension/schema/plugin.json b/packages/shortcuts-extension/schema/plugin.json deleted file mode 100644 index 15eb4eb2d6fc..000000000000 --- a/packages/shortcuts-extension/schema/plugin.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "jupyter.lab.setting-deprecated": true, - "jupyter.lab.setting-icon-class": "jp-KeyboardIcon", - "jupyter.lab.setting-icon-label": "Keyboard Shortcuts", - "title": "Keyboard Shortcuts", - "description": "Keyboard shortcut settings for JupyterLab.", - "properties": {}, - "oneOf": [{ "$ref": "#/definitions/shortcut" }], - "type": "object", - "definitions": { - "shortcut": { - "properties": { - "command": { "type": "string" }, - "keys": { - "items": { "type": "string" }, - "minItems": 1, - "type": "array" - }, - "selector": { "type": "string" } - }, - "type": "object" - } - } -} diff --git a/packages/shortcuts-extension/src/index.ts b/packages/shortcuts-extension/src/index.ts index fa4269e46e80..4587af8820c3 100644 --- a/packages/shortcuts-extension/src/index.ts +++ b/packages/shortcuts-extension/src/index.ts @@ -18,97 +18,6 @@ import { import { DisposableSet, IDisposable } from '@lumino/disposable'; -/** - * The ASCII record separator character. - */ -const RECORD_SEPARATOR = String.fromCharCode(30); - -/** - * This plugin and its schema are deprecated and will be removed in a future - * version of JupyterLab. This plugin will load old keyboard shortcuts and add - * them to the new keyboard shortcuts plugin below before removing the old - * shortcuts. - */ -const plugin: JupyterFrontEndPlugin = { - id: '@jupyterlab/shortcuts-extension:plugin', - requires: [ISettingRegistry], - activate: async (app: JupyterFrontEnd, registry: ISettingRegistry) => { - try { - const old = await registry.load(plugin.id); - const settings = await registry.load(shortcuts.id); - const keys = Object.keys(old.user); - const deprecated: ISettingRegistry.IShortcut[] = []; - const port = (deprecated: ISettingRegistry.IShortcut[]) => { - if (!deprecated.length) { - return; - } - - const memo: { - [keys: string]: { [selector: string]: null }; - } = {}; - const shortcuts = settings.user - .shortcuts as ISettingRegistry.IShortcut[]; - - // Add the current shortcuts into the memo. - shortcuts.forEach(shortcut => { - const keys = shortcut.keys.join(RECORD_SEPARATOR); - const { selector } = shortcut; - - if (!keys) { - return; - } - if (!(keys in memo)) { - memo[keys] = {}; - } - if (!(selector in memo[keys])) { - memo[keys][selector] = null; - } - }); - - // Add deprecated shortcuts that don't exist to the current list. - deprecated.forEach(shortcut => { - const { selector } = shortcut; - const keys = shortcut.keys.join(RECORD_SEPARATOR); - - if (!(keys in memo)) { - memo[keys] = {}; - } - if (!(selector in memo[keys])) { - memo[keys][selector] = null; - shortcuts.push(shortcut); - } - }); - - // Save the reconciled list. - void settings.set('shortcuts', shortcuts); - }; - - if (!keys.length) { - return; - } - keys.forEach(key => { - const { command, keys, selector } = old.user[ - key - ] as ISettingRegistry.IShortcut; - - // Only port shortcuts over if they are valid. - if (command && selector && keys && keys.length) { - deprecated.push({ command, keys, selector }); - } - }); - - // Port the deprecated shortcuts to the new plugin. - port(deprecated); - - // Remove all old shortcuts; - void old.save('{}'); - } catch (error) { - console.error(`Loading ${plugin.id} failed.`, error); - } - }, - autoStart: true -}; - /** * The default shortcuts extension. * @@ -259,11 +168,9 @@ List of Keyboard Shortcuts`; }; /** - * Export the plugins as default. + * Export the shortcut plugin as default. */ -const plugins: JupyterFrontEndPlugin[] = [plugin, shortcuts]; - -export default plugins; +export default shortcuts; /** * A namespace for private module data.