UNPKG

@jbrowse/core

Version:

JBrowse 2 core libraries used by plugins

406 lines (405 loc) 15.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const mobx_state_tree_1 = require("mobx-state-tree"); const CorePlugin_1 = __importDefault(require("./CorePlugin")); const PhasedScheduler_1 = __importDefault(require("./PhasedScheduler")); const ReExports_1 = __importDefault(require("./ReExports")); const configuration_1 = require("./configuration"); const AdapterType_1 = __importDefault(require("./pluggableElementTypes/AdapterType")); const AddTrackWorkflowType_1 = __importDefault(require("./pluggableElementTypes/AddTrackWorkflowType")); const ConnectionType_1 = __importDefault(require("./pluggableElementTypes/ConnectionType")); const DisplayType_1 = __importDefault(require("./pluggableElementTypes/DisplayType")); const InternetAccountType_1 = __importDefault(require("./pluggableElementTypes/InternetAccountType")); const RpcMethodType_1 = __importDefault(require("./pluggableElementTypes/RpcMethodType")); const TextSearchAdapterType_1 = __importDefault(require("./pluggableElementTypes/TextSearchAdapterType")); const TrackType_1 = __importDefault(require("./pluggableElementTypes/TrackType")); const ViewType_1 = __importDefault(require("./pluggableElementTypes/ViewType")); const WidgetType_1 = __importDefault(require("./pluggableElementTypes/WidgetType")); const RendererType_1 = __importDefault(require("./pluggableElementTypes/renderers/RendererType")); const jexl_1 = __importDefault(require("./util/jexl")); class TypeRecord { constructor(typeName, baseClass) { this.typeName = typeName; this.baseClass = baseClass; this.registeredTypes = {}; } add(name, t) { this.registeredTypes[name] = t; } has(name) { return name in this.registeredTypes; } get(name) { if (!this.has(name)) { throw new Error(`${this.typeName} '${name}' not found, perhaps its plugin is not loaded or its plugin has not added it.`); } return this.registeredTypes[name]; } all() { return Object.values(this.registeredTypes); } } class PluginManager { constructor(initialPlugins = []) { this.plugins = []; this.jexl = (0, jexl_1.default)(); this.pluginMetadata = {}; this.runtimePluginDefinitions = []; this.elementCreationSchedule = new PhasedScheduler_1.default('renderer', 'adapter', 'text search adapter', 'display', 'track', 'connection', 'view', 'widget', 'rpc method', 'internet account', 'add track workflow'); this.rendererTypes = new TypeRecord('RendererType', RendererType_1.default); this.adapterTypes = new TypeRecord('AdapterType', AdapterType_1.default); this.textSearchAdapterTypes = new TypeRecord('TextSearchAdapterType', TextSearchAdapterType_1.default); this.trackTypes = new TypeRecord('TrackType', TrackType_1.default); this.displayTypes = new TypeRecord('DisplayType', DisplayType_1.default); this.connectionTypes = new TypeRecord('ConnectionType', ConnectionType_1.default); this.viewTypes = new TypeRecord('ViewType', ViewType_1.default); this.widgetTypes = new TypeRecord('WidgetType', WidgetType_1.default); this.rpcMethods = new TypeRecord('RpcMethodType', RpcMethodType_1.default); this.addTrackWidgets = new TypeRecord('AddTrackWorkflow', AddTrackWorkflowType_1.default); this.internetAccountTypes = new TypeRecord('InternetAccountType', InternetAccountType_1.default); this.configured = false; this.extensionPoints = new Map(); this.jbrequireCache = new Map(); this.lib = ReExports_1.default; this.load = (lib) => { if (!this.jbrequireCache.has(lib)) { this.jbrequireCache.set(lib, lib(this)); } return this.jbrequireCache.get(lib); }; this.jbrequire = (lib) => { if (typeof lib === 'string') { const pack = this.lib[lib]; if (!pack) { throw new TypeError(`No jbrequire re-export defined for package '${lib}'. If this package must be shared between plugins, add it to ReExports.js. If it does not need to be shared, just import it normally.`); } return pack; } else if (typeof lib === 'function') { return this.load(lib); } else if (lib.default) { console.warn('initiated jbrequire on a {default:Function}'); return this.jbrequire(lib.default); } throw new TypeError('lib passed to jbrequire must be either a string or a function'); }; this.addPlugin({ plugin: new CorePlugin_1.default(), metadata: { isCore: true, }, }); for (const plugin of initialPlugins) { this.addPlugin(plugin); } } pluginConfigurationNamespacedSchemas() { const configurationSchemas = {}; for (const plugin of this.plugins) { if (plugin.configurationSchema) { configurationSchemas[plugin.name] = plugin.configurationSchema; } } return configurationSchemas; } pluginConfigurationUnnamespacedSchemas() { let configurationSchemas = {}; for (const plugin of this.plugins) { if (plugin.configurationSchemaUnnamespaced) { configurationSchemas = { ...configurationSchemas, ...plugin.configurationSchemaUnnamespaced, }; } } return configurationSchemas; } pluginConfigurationRootSchemas() { let configurationSchemas = {}; for (const plugin of this.plugins) { if (plugin.rootConfigurationSchema) { configurationSchemas = { ...configurationSchemas, ...plugin.rootConfigurationSchema(this), }; } } return configurationSchemas; } addPlugin(load) { if (this.configured) { throw new Error('JBrowse already configured, cannot add plugins'); } const [plugin, metadata = {}] = 'install' in load && 'configure' in load ? [load, {}] : [load.plugin, load.metadata]; if (this.plugins.includes(plugin)) { throw new Error('plugin already installed'); } this.pluginMetadata[plugin.name] = metadata; if ('definition' in load) { this.runtimePluginDefinitions.push(load.definition); } plugin.install(this); this.plugins.push(plugin); return this; } getPlugin(name) { return this.plugins.find(p => p.name === name); } hasPlugin(name) { return this.getPlugin(name) !== undefined; } createPluggableElements() { if (this.elementCreationSchedule) { this.elementCreationSchedule.run(); this.elementCreationSchedule = undefined; } return this; } setRootModel(rootModel) { this.rootModel = rootModel; return this; } configure() { if (this.configured) { throw new Error('already configured'); } for (const plugin of this.plugins) { plugin.configure(this); } this.configured = true; return this; } getElementTypeRecord(groupName) { switch (groupName) { case 'adapter': return this.adapterTypes; case 'text search adapter': return this.textSearchAdapterTypes; case 'connection': return this.connectionTypes; case 'widget': return this.widgetTypes; case 'renderer': return this.rendererTypes; case 'display': return this.displayTypes; case 'track': return this.trackTypes; case 'view': return this.viewTypes; case 'rpc method': return this.rpcMethods; case 'internet account': return this.internetAccountTypes; case 'add track workflow': return this.addTrackWidgets; default: throw new Error(`invalid element type '${groupName}'`); } } addElementType(groupName, creationCallback) { var _a; if (typeof creationCallback !== 'function') { throw new Error('must provide a callback function that returns the new type object'); } const typeRecord = this.getElementTypeRecord(groupName); (_a = this.elementCreationSchedule) === null || _a === void 0 ? void 0 : _a.add(groupName, () => { const newElement = creationCallback(this); if (!newElement.name) { throw new Error(`cannot add a ${groupName} with no name`); } if (typeRecord.has(newElement.name)) { console.warn(`${groupName} ${newElement.name} already registered, cannot register it again`); } else { typeRecord.add(newElement.name, this.evaluateExtensionPoint('Core-extendPluggableElement', newElement)); } }); return this; } getElementType(groupName, typeName) { return this.getElementTypeRecord(groupName).get(typeName); } getElementTypesInGroup(groupName) { return this.getElementTypeRecord(groupName).all(); } getViewElements() { return this.getElementTypesInGroup('view'); } getTrackElements() { return this.getElementTypesInGroup('track'); } getConnectionElements() { return this.getElementTypesInGroup('connection'); } getAddTrackWorkflowElements() { return this.getElementTypesInGroup('add track workflow'); } getRpcElements() { return this.getElementTypesInGroup('rpc method'); } getDisplayElements() { return this.getElementTypesInGroup('display'); } getAdapterElements() { return this.getElementTypesInGroup('adapter'); } pluggableMstType(groupName, fieldName, fallback = mobx_state_tree_1.types.maybe(mobx_state_tree_1.types.null)) { const pluggableTypes = this.getElementTypeRecord(groupName) .all() .map(t => t[fieldName]) .filter(t => (0, mobx_state_tree_1.isType)(t) && (0, mobx_state_tree_1.isModelType)(t)); if (pluggableTypes.length === 0 && typeof jest === 'undefined') { console.warn(`No pluggable types found matching ('${groupName}','${fieldName}')`); return fallback; } return mobx_state_tree_1.types.union(...pluggableTypes); } pluggableConfigSchemaType(typeGroup, fieldName = 'configSchema') { const pluggableTypes = this.getElementTypeRecord(typeGroup) .all() .map(t => t[fieldName]) .filter(t => (0, configuration_1.isBareConfigurationSchemaType)(t)); if (pluggableTypes.length === 0) { pluggableTypes.push((0, configuration_1.ConfigurationSchema)('Null', {})); } return mobx_state_tree_1.types.union(...pluggableTypes); } getRendererType(typeName) { return this.rendererTypes.get(typeName); } getRendererTypes() { return this.rendererTypes.all(); } getAdapterType(typeName) { return this.adapterTypes.get(typeName); } getTextSearchAdapterType(typeName) { return this.textSearchAdapterTypes.get(typeName); } getTrackType(typeName) { return this.trackTypes.get(typeName); } getDisplayType(typeName) { return this.displayTypes.get(typeName); } getViewType(typeName) { return this.viewTypes.get(typeName); } getAddTrackWorkflow(typeName) { return this.addTrackWidgets.get(typeName); } getWidgetType(typeName) { return this.widgetTypes.get(typeName); } getConnectionType(typeName) { return this.connectionTypes.get(typeName); } getRpcMethodType(methodName) { return this.rpcMethods.get(methodName); } getInternetAccountType(name) { return this.internetAccountTypes.get(name); } addRendererType(cb) { return this.addElementType('renderer', cb); } addAdapterType(cb) { return this.addElementType('adapter', cb); } addTextSearchAdapterType(cb) { return this.addElementType('text search adapter', cb); } addTrackType(cb) { const callback = () => { const track = cb(this); const displays = this.getElementTypesInGroup('display'); for (const display of displays) { if (display.trackType === track.name && !track.displayTypes.includes(display)) { track.addDisplayType(display); } } return track; }; return this.addElementType('track', callback); } addDisplayType(cb) { return this.addElementType('display', cb); } addViewType(cb) { const callback = () => { const newView = cb(this); const displays = this.getElementTypesInGroup('display'); for (const display of displays) { if ((display.viewType === newView.name || display.viewType === newView.extendedName) && !newView.displayTypes.includes(display)) { newView.addDisplayType(display); } } return newView; }; return this.addElementType('view', callback); } addWidgetType(cb) { return this.addElementType('widget', cb); } addConnectionType(cb) { return this.addElementType('connection', cb); } addRpcMethod(cb) { return this.addElementType('rpc method', cb); } addInternetAccountType(cb) { return this.addElementType('internet account', cb); } addAddTrackWorkflowType(cb) { return this.addElementType('add track workflow', cb); } addToExtensionPoint(extensionPointName, callback) { let callbacks = this.extensionPoints.get(extensionPointName); if (!callbacks) { callbacks = []; this.extensionPoints.set(extensionPointName, callbacks); } callbacks.push(callback); } evaluateExtensionPoint(extensionPointName, extendee, props) { const callbacks = this.extensionPoints.get(extensionPointName); let accumulator = extendee; if (callbacks) { for (const callback of callbacks) { try { accumulator = callback(accumulator, props); } catch (error) { console.error(error); } } } return accumulator; } async evaluateAsyncExtensionPoint(extensionPointName, extendee, props) { const callbacks = this.extensionPoints.get(extensionPointName); let accumulator = extendee; if (callbacks) { for (const callback of callbacks) { try { accumulator = await callback(accumulator, props); } catch (error) { console.error(error); } } } return accumulator; } } exports.default = PluginManager;