255 lines
5.3 KiB
JavaScript
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 = [];
|
|
|
|
}
|
|
|
|
}
|