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 registed 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 regisered 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]; } 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; } }