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