diff options
Diffstat (limited to 'present/js/controllers/plugins.js')
| -rw-r--r-- | present/js/controllers/plugins.js | 254 | 
1 files changed, 254 insertions, 0 deletions
| diff --git a/present/js/controllers/plugins.js b/present/js/controllers/plugins.js new file mode 100644 index 0000000..88f57bf --- /dev/null +++ b/present/js/controllers/plugins.js @@ -0,0 +1,254 @@ +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 = []; + +	} + +} | 
