@jupyterlab/application
Version:
JupyterLab - Application
318 lines • 12.7 kB
JavaScript
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { PageConfig } from '@jupyterlab/coreutils';
import { Base64ModelFactory } from '@jupyterlab/docregistry';
import { ServiceManager } from '@jupyterlab/services';
import { PromiseDelegate, Token } from '@lumino/coreutils';
import { JupyterFrontEnd } from './frontend';
import { createRendermimePlugins } from './mimerenderers';
import { LabShell } from './shell';
import { LabStatus } from './status';
/**
* JupyterLab is the main application class. It is instantiated once and shared.
*/
export class JupyterLab extends JupyterFrontEnd {
/**
* Construct a new JupyterLab object.
*/
constructor(options = { shell: new LabShell() }) {
super({
...options,
shell: options.shell || new LabShell(),
serviceManager: options.serviceManager ||
new ServiceManager({
standby: () => {
return !this._info.isConnected || 'when-hidden';
}
})
});
/**
* The name of the JupyterLab application.
*/
this.name = PageConfig.getOption('appName') || 'JupyterLab';
/**
* A namespace/prefix plugins may use to denote their provenance.
*/
this.namespace = PageConfig.getOption('appNamespace') || this.name;
/**
* A list of all errors encountered when registering plugins.
*/
this.registerPluginErrors = [];
/**
* The application busy and dirty status signals and flags.
*/
this.status = new LabStatus(this);
/**
* The version of the JupyterLab application.
*/
this.version = PageConfig.getOption('appVersion') || 'unknown';
this._info = JupyterLab.defaultInfo;
this._allPluginsActivated = new PromiseDelegate();
// Create an IInfo dictionary from the options to override the defaults.
const info = Object.keys(JupyterLab.defaultInfo).reduce((acc, val) => {
if (val in options) {
acc[val] = JSON.parse(JSON.stringify(options[val]));
}
return acc;
}, {});
// Populate application info.
this._info = { ...JupyterLab.defaultInfo, ...info };
this.restored = this.shell.restored
.then(async () => {
const activated = [];
const deferred = this.activateDeferredPlugins().catch(error => {
console.error('Error when activating deferred plugins\n:', error);
});
activated.push(deferred);
if (this._info.deferred) {
const customizedDeferred = Promise.all(this._info.deferred.matches.map(pluginID => this.activatePlugin(pluginID))).catch(error => {
console.error('Error when activating customized list of deferred plugins:\n', error);
});
activated.push(customizedDeferred);
}
Promise.all(activated)
.then(() => {
this._allPluginsActivated.resolve();
})
.catch(() => undefined);
})
.catch(() => undefined);
// Populate application paths override the defaults if necessary.
const defaultURLs = JupyterLab.defaultPaths.urls;
const defaultDirs = JupyterLab.defaultPaths.directories;
const optionURLs = (options.paths && options.paths.urls) || {};
const optionDirs = (options.paths && options.paths.directories) || {};
this._paths = {
urls: Object.keys(defaultURLs).reduce((acc, key) => {
if (key in optionURLs) {
const value = optionURLs[key];
acc[key] = value;
}
else {
acc[key] = defaultURLs[key];
}
return acc;
}, {}),
directories: Object.keys(JupyterLab.defaultPaths.directories).reduce((acc, key) => {
if (key in optionDirs) {
const value = optionDirs[key];
acc[key] = value;
}
else {
acc[key] = defaultDirs[key];
}
return acc;
}, {})
};
if (this._info.devMode) {
this.shell.addClass('jp-mod-devMode');
}
// Add initial model factory.
this.docRegistry.addModelFactory(new Base64ModelFactory());
if (options.mimeExtensions) {
for (const plugin of createRendermimePlugins(options.mimeExtensions)) {
this.registerPlugin(plugin);
}
}
}
/**
* The JupyterLab application information dictionary.
*/
get info() {
return this._info;
}
/**
* The JupyterLab application paths dictionary.
*/
get paths() {
return this._paths;
}
/**
* Promise that resolves when all the plugins are activated, including the deferred.
*/
get allPluginsActivated() {
return this._allPluginsActivated.promise;
}
/**
* Register plugins from a plugin module.
*
* @param mod - The plugin module to register.
*/
registerPluginModule(mod) {
let data = mod.default;
// Handle commonjs exports.
if (!mod.hasOwnProperty('__esModule')) {
data = mod;
}
if (!Array.isArray(data)) {
data = [data];
}
data.forEach(item => {
try {
this.registerPlugin(item);
}
catch (error) {
this.registerPluginErrors.push(error);
}
});
}
/**
* Register the plugins from multiple plugin modules.
*
* @param mods - The plugin modules to register.
*/
registerPluginModules(mods) {
mods.forEach(mod => {
this.registerPluginModule(mod);
});
}
/**
* Override keydown handling to prevent command shortcuts from preventing user input.
*
* This introduces a slight delay to the command invocation, but no delay to user input.
*/
evtKeydown(keyDownEvent) {
const permissionToExecute = new PromiseDelegate();
// Hold the execution of any keybinding until we know if this event would cause text insertion
this.commands.holdKeyBindingExecution(keyDownEvent, permissionToExecute.promise);
// Process the key immediately to invoke the prevent default handlers as needed
this.commands.processKeydownEvent(keyDownEvent);
// If we do not know the target, we cannot check if input would be inserted
// as there is no target to attach the `beforeinput` event listener; in that
// case we just permit execution immediately (this may happen for programmatic
// uses of keydown)
const target = keyDownEvent.target;
if (!target) {
return permissionToExecute.resolve(true);
}
let onBeforeInput = null;
let onBeforeKeyUp = null;
const disconnectListeners = () => {
if (onBeforeInput) {
target.removeEventListener('beforeinput', onBeforeInput);
}
if (onBeforeKeyUp) {
target.removeEventListener('keyup', onBeforeKeyUp);
}
};
// Permit the execution conditionally, depending on whether the event would lead to text insertion
const causesInputPromise = Promise.race([
new Promise(resolve => {
onBeforeInput = (inputEvent) => {
switch (inputEvent.inputType) {
case 'historyUndo':
case 'historyRedo': {
if (inputEvent.target instanceof Element &&
inputEvent.target.closest('[data-jp-undoer]')) {
// Allow to use custom undo/redo bindings on `jpUndoer`s
inputEvent.preventDefault();
disconnectListeners();
return resolve(false);
}
break;
}
case 'insertLineBreak': {
if (inputEvent.target instanceof Element &&
inputEvent.target.closest('.jp-Cell')) {
// Allow to override the default action of Shift + Enter on cells as this is used for cell execution
inputEvent.preventDefault();
disconnectListeners();
return resolve(false);
}
break;
}
}
disconnectListeners();
return resolve(true);
};
target.addEventListener('beforeinput', onBeforeInput, { once: true });
}),
new Promise(resolve => {
onBeforeKeyUp = (keyUpEvent) => {
if (keyUpEvent.code === keyDownEvent.code) {
disconnectListeners();
return resolve(false);
}
};
target.addEventListener('keyup', onBeforeKeyUp, { once: true });
}),
new Promise(resolve => {
setTimeout(() => {
disconnectListeners();
return resolve(false);
}, Private.INPUT_GUARD_TIMEOUT);
})
]);
causesInputPromise
.then(willCauseInput => {
permissionToExecute.resolve(!willCauseInput);
})
.catch(console.warn);
}
}
/**
* The namespace for `JupyterLab` class statics.
*/
(function (JupyterLab) {
/**
* The layout restorer token.
*/
JupyterLab.IInfo = new Token('@jupyterlab/application:IInfo', 'A service providing metadata about the current application, including disabled extensions and whether dev mode is enabled.');
/**
* The default JupyterLab application info.
*/
JupyterLab.defaultInfo = {
devMode: PageConfig.getOption('devMode').toLowerCase() === 'true',
deferred: { patterns: [], matches: [] },
disabled: { patterns: [], matches: [] },
mimeExtensions: [],
availablePlugins: [],
filesCached: PageConfig.getOption('cacheFiles').toLowerCase() === 'true',
isConnected: true
};
/**
* The default JupyterLab application paths.
*/
JupyterLab.defaultPaths = {
urls: {
base: PageConfig.getOption('baseUrl'),
notFound: PageConfig.getOption('notFoundUrl'),
app: PageConfig.getOption('appUrl'),
doc: PageConfig.getOption('docUrl'),
static: PageConfig.getOption('staticUrl'),
settings: PageConfig.getOption('settingsUrl'),
themes: PageConfig.getOption('themesUrl'),
translations: PageConfig.getOption('translationsApiUrl'),
hubHost: PageConfig.getOption('hubHost') || undefined,
hubPrefix: PageConfig.getOption('hubPrefix') || undefined,
hubUser: PageConfig.getOption('hubUser') || undefined,
hubServerName: PageConfig.getOption('hubServerName') || undefined
},
directories: {
appSettings: PageConfig.getOption('appSettingsDir'),
schemas: PageConfig.getOption('schemasDir'),
static: PageConfig.getOption('staticDir'),
templates: PageConfig.getOption('templatesDir'),
themes: PageConfig.getOption('themesDir'),
userSettings: PageConfig.getOption('userSettingsDir'),
serverRoot: PageConfig.getOption('serverRoot'),
workspaces: PageConfig.getOption('workspacesDir')
}
};
})(JupyterLab || (JupyterLab = {}));
/**
* A namespace for module-private functionality.
*/
var Private;
(function (Private) {
/**
* The delay for invoking a command introduced by user input guard.
* Decreasing this value may lead to commands incorrectly triggering
* on user input. Increasing this value will lead to longer delay for
* command invocation. Note that user input is never delayed.
*
* The value represents the number in milliseconds.
*/
Private.INPUT_GUARD_TIMEOUT = 10;
})(Private || (Private = {}));
//# sourceMappingURL=lab.js.map