deployment-talks/slides/js/controllers/plugins.js

255 lines
5.3 KiB
JavaScript

import { loadScript } from '../utils/loader.js'
/**
* Manages loading and registering of reveal.js plugins.
*/
export default class Plugins {
constructor( reveal ) {
this.Reveal = reveal;
// Flags our current state (idle -> loading -> loaded)
this.state = 'idle';
// An id:instance map of currently registered plugins
this.registeredPlugins = {};
this.asyncDependencies = [];
}
/**
* Loads reveal.js dependencies, registers and
* initializes plugins.
*
* Plugins are direct references to a reveal.js plugin
* object that we register and initialize after any
* synchronous dependencies have loaded.
*
* Dependencies are defined via the 'dependencies' config
* option and will be loaded prior to starting reveal.js.
* Some dependencies may have an 'async' flag, if so they
* will load after reveal.js has been started up.
*/
load( plugins, dependencies ) {
this.state = 'loading';
plugins.forEach( this.registerPlugin.bind( this ) );
return new Promise( resolve => {
let scripts = [],
scriptsToLoad = 0;
dependencies.forEach( s => {
// Load if there's no condition or the condition is truthy
if( !s.condition || s.condition() ) {
if( s.async ) {
this.asyncDependencies.push( s );
}
else {
scripts.push( s );
}
}
} );
if( scripts.length ) {
scriptsToLoad = scripts.length;
const scriptLoadedCallback = (s) => {
if( s && typeof s.callback === 'function' ) s.callback();
if( --scriptsToLoad === 0 ) {
this.initPlugins().then( resolve );
}
};
// Load synchronous scripts
scripts.forEach( s => {
if( typeof s.id === 'string' ) {
this.registerPlugin( s );
scriptLoadedCallback( s );
}
else if( typeof s.src === 'string' ) {
loadScript( s.src, () => scriptLoadedCallback(s) );
}
else {
console.warn( 'Unrecognized plugin format', s );
scriptLoadedCallback();
}
} );
}
else {
this.initPlugins().then( resolve );
}
} );
}
/**
* Initializes our plugins and waits for them to be ready
* before proceeding.
*/
initPlugins() {
return new Promise( resolve => {
let pluginValues = Object.values( this.registeredPlugins );
let pluginsToInitialize = pluginValues.length;
// If there are no plugins, skip this step
if( pluginsToInitialize === 0 ) {
this.loadAsync().then( resolve );
}
// ... otherwise initialize plugins
else {
let initNextPlugin;
let afterPlugInitialized = () => {
if( --pluginsToInitialize === 0 ) {
this.loadAsync().then( resolve );
}
else {
initNextPlugin();
}
};
let i = 0;
// Initialize plugins serially
initNextPlugin = () => {
let plugin = pluginValues[i++];
// If the plugin has an 'init' method, invoke it
if( typeof plugin.init === 'function' ) {
let promise = plugin.init( this.Reveal );
// If the plugin returned a Promise, wait for it
if( promise && typeof promise.then === 'function' ) {
promise.then( afterPlugInitialized );
}
else {
afterPlugInitialized();
}
}
else {
afterPlugInitialized();
}
}
initNextPlugin();
}
} )
}
/**
* Loads all async reveal.js dependencies.
*/
loadAsync() {
this.state = 'loaded';
if( this.asyncDependencies.length ) {
this.asyncDependencies.forEach( s => {
loadScript( s.src, s.callback );
} );
}
return Promise.resolve();
}
/**
* Registers a new plugin with this reveal.js instance.
*
* reveal.js waits for all registered plugins to initialize
* before considering itself ready, as long as the plugin
* is registered before calling `Reveal.initialize()`.
*/
registerPlugin( plugin ) {
// Backwards compatibility to make reveal.js ~3.9.0
// plugins work with reveal.js 4.0.0
if( arguments.length === 2 && typeof arguments[0] === 'string' ) {
plugin = arguments[1];
plugin.id = arguments[0];
}
// Plugin can optionally be a function which we call
// to create an instance of the plugin
else if( typeof plugin === 'function' ) {
plugin = plugin();
}
let id = plugin.id;
if( typeof id !== 'string' ) {
console.warn( 'Unrecognized plugin format; can\'t find plugin.id', plugin );
}
else if( this.registeredPlugins[id] === undefined ) {
this.registeredPlugins[id] = plugin;
// If a plugin is registered after reveal.js is loaded,
// initialize it right away
if( this.state === 'loaded' && typeof plugin.init === 'function' ) {
plugin.init( this.Reveal );
}
}
else {
console.warn( 'reveal.js: "'+ id +'" plugin has already been registered' );
}
}
/**
* Checks if a specific plugin has been registered.
*
* @param {String} id Unique plugin identifier
*/
hasPlugin( id ) {
return !!this.registeredPlugins[id];
}
/**
* Returns the specific plugin instance, if a plugin
* with the given ID has been registered.
*
* @param {String} id Unique plugin identifier
*/
getPlugin( id ) {
return this.registeredPlugins[id];
}
getRegisteredPlugins() {
return this.registeredPlugins;
}
destroy() {
Object.values( this.registeredPlugins ).forEach( plugin => {
if( typeof plugin.destroy === 'function' ) {
plugin.destroy();
}
} );
this.registeredPlugins = {};
this.asyncDependencies = [];
}
}