You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

255 line
5.3 KiB

  1. import { loadScript } from '../utils/loader.js'
  2. /**
  3. * Manages loading and registering of reveal.js plugins.
  4. */
  5. export default class Plugins {
  6. constructor( reveal ) {
  7. this.Reveal = reveal;
  8. // Flags our current state (idle -> loading -> loaded)
  9. this.state = 'idle';
  10. // An id:instance map of currently registered plugins
  11. this.registeredPlugins = {};
  12. this.asyncDependencies = [];
  13. }
  14. /**
  15. * Loads reveal.js dependencies, registers and
  16. * initializes plugins.
  17. *
  18. * Plugins are direct references to a reveal.js plugin
  19. * object that we register and initialize after any
  20. * synchronous dependencies have loaded.
  21. *
  22. * Dependencies are defined via the 'dependencies' config
  23. * option and will be loaded prior to starting reveal.js.
  24. * Some dependencies may have an 'async' flag, if so they
  25. * will load after reveal.js has been started up.
  26. */
  27. load( plugins, dependencies ) {
  28. this.state = 'loading';
  29. plugins.forEach( this.registerPlugin.bind( this ) );
  30. return new Promise( resolve => {
  31. let scripts = [],
  32. scriptsToLoad = 0;
  33. dependencies.forEach( s => {
  34. // Load if there's no condition or the condition is truthy
  35. if( !s.condition || s.condition() ) {
  36. if( s.async ) {
  37. this.asyncDependencies.push( s );
  38. }
  39. else {
  40. scripts.push( s );
  41. }
  42. }
  43. } );
  44. if( scripts.length ) {
  45. scriptsToLoad = scripts.length;
  46. const scriptLoadedCallback = (s) => {
  47. if( s && typeof s.callback === 'function' ) s.callback();
  48. if( --scriptsToLoad === 0 ) {
  49. this.initPlugins().then( resolve );
  50. }
  51. };
  52. // Load synchronous scripts
  53. scripts.forEach( s => {
  54. if( typeof s.id === 'string' ) {
  55. this.registerPlugin( s );
  56. scriptLoadedCallback( s );
  57. }
  58. else if( typeof s.src === 'string' ) {
  59. loadScript( s.src, () => scriptLoadedCallback(s) );
  60. }
  61. else {
  62. console.warn( 'Unrecognized plugin format', s );
  63. scriptLoadedCallback();
  64. }
  65. } );
  66. }
  67. else {
  68. this.initPlugins().then( resolve );
  69. }
  70. } );
  71. }
  72. /**
  73. * Initializes our plugins and waits for them to be ready
  74. * before proceeding.
  75. */
  76. initPlugins() {
  77. return new Promise( resolve => {
  78. let pluginValues = Object.values( this.registeredPlugins );
  79. let pluginsToInitialize = pluginValues.length;
  80. // If there are no plugins, skip this step
  81. if( pluginsToInitialize === 0 ) {
  82. this.loadAsync().then( resolve );
  83. }
  84. // ... otherwise initialize plugins
  85. else {
  86. let initNextPlugin;
  87. let afterPlugInitialized = () => {
  88. if( --pluginsToInitialize === 0 ) {
  89. this.loadAsync().then( resolve );
  90. }
  91. else {
  92. initNextPlugin();
  93. }
  94. };
  95. let i = 0;
  96. // Initialize plugins serially
  97. initNextPlugin = () => {
  98. let plugin = pluginValues[i++];
  99. // If the plugin has an 'init' method, invoke it
  100. if( typeof plugin.init === 'function' ) {
  101. let promise = plugin.init( this.Reveal );
  102. // If the plugin returned a Promise, wait for it
  103. if( promise && typeof promise.then === 'function' ) {
  104. promise.then( afterPlugInitialized );
  105. }
  106. else {
  107. afterPlugInitialized();
  108. }
  109. }
  110. else {
  111. afterPlugInitialized();
  112. }
  113. }
  114. initNextPlugin();
  115. }
  116. } )
  117. }
  118. /**
  119. * Loads all async reveal.js dependencies.
  120. */
  121. loadAsync() {
  122. this.state = 'loaded';
  123. if( this.asyncDependencies.length ) {
  124. this.asyncDependencies.forEach( s => {
  125. loadScript( s.src, s.callback );
  126. } );
  127. }
  128. return Promise.resolve();
  129. }
  130. /**
  131. * Registers a new plugin with this reveal.js instance.
  132. *
  133. * reveal.js waits for all registered plugins to initialize
  134. * before considering itself ready, as long as the plugin
  135. * is registered before calling `Reveal.initialize()`.
  136. */
  137. registerPlugin( plugin ) {
  138. // Backwards compatibility to make reveal.js ~3.9.0
  139. // plugins work with reveal.js 4.0.0
  140. if( arguments.length === 2 && typeof arguments[0] === 'string' ) {
  141. plugin = arguments[1];
  142. plugin.id = arguments[0];
  143. }
  144. // Plugin can optionally be a function which we call
  145. // to create an instance of the plugin
  146. else if( typeof plugin === 'function' ) {
  147. plugin = plugin();
  148. }
  149. let id = plugin.id;
  150. if( typeof id !== 'string' ) {
  151. console.warn( 'Unrecognized plugin format; can\'t find plugin.id', plugin );
  152. }
  153. else if( this.registeredPlugins[id] === undefined ) {
  154. this.registeredPlugins[id] = plugin;
  155. // If a plugin is registered after reveal.js is loaded,
  156. // initialize it right away
  157. if( this.state === 'loaded' && typeof plugin.init === 'function' ) {
  158. plugin.init( this.Reveal );
  159. }
  160. }
  161. else {
  162. console.warn( 'reveal.js: "'+ id +'" plugin has already been registered' );
  163. }
  164. }
  165. /**
  166. * Checks if a specific plugin has been registered.
  167. *
  168. * @param {String} id Unique plugin identifier
  169. */
  170. hasPlugin( id ) {
  171. return !!this.registeredPlugins[id];
  172. }
  173. /**
  174. * Returns the specific plugin instance, if a plugin
  175. * with the given ID has been registered.
  176. *
  177. * @param {String} id Unique plugin identifier
  178. */
  179. getPlugin( id ) {
  180. return this.registeredPlugins[id];
  181. }
  182. getRegisteredPlugins() {
  183. return this.registeredPlugins;
  184. }
  185. destroy() {
  186. Object.values( this.registeredPlugins ).forEach( plugin => {
  187. if( typeof plugin.destroy === 'function' ) {
  188. plugin.destroy();
  189. }
  190. } );
  191. this.registeredPlugins = {};
  192. this.asyncDependencies = [];
  193. }
  194. }