/* global log -- eslint */
/**
* @file Plugin hooks for IITC. This file defines the infrastructure for managing and executing hooks,
* which are used to trigger custom plugin actions at specific points in the application lifecycle.
* Plugins may listen to any number of events by specifying the name of the event and providing a function
* to execute when an event occurs. Callbacks receive additional data created by the event as their first parameter.
* The value is always an Object that contains more details.
*
* For example, this line will listen for portals to be added and print the data generated by the event to the console:
* `window.addHook('portalAdded', function(data) { log.log(data) });`
*
* Boot hook: booting is handled differently because IITC may not yet be available.
* Have a look at the plugins in plugins/. All code before `// PLUGIN START` and after `// PLUGIN END` is required
* to successfully boot the plugin.
*
* Description of available hook events:
* - `portalSelected`: Triggered when a portal on the map is selected or unselected.
* Provides the GUID of both the selected and unselected portal.
* - `mapDataRefreshStart`: Triggered at the start of refreshing map data.
* - `mapDataEntityInject`: Triggered just as we start to render data.
* Allows injecting cached entities into the map render.
* - `mapDataRefreshEnd`: Triggered when the map data load is complete.
* - `portalAdded`: Triggered when a portal has been received and is about to be added to its layer group.
* Does not guarantee the portal will be visible or shown soon.
* Portals added to hidden layers may never be shown.
* Injection point is in `code/map_data.js#renderPortal` near the end.
* Provides the Leaflet CircleMarker for the portal in the "portal" variable.
* - `linkAdded`: Triggered when a link is about to be added to the map.
* - `fieldAdded`: Triggered when a field is about to be added to the map.
* - `portalRemoved`: Triggered when a portal has been removed.
* - `linkRemoved`: Triggered when a link has been removed.
* - `fieldRemoved`: Triggered when a field has been removed.
* - `portalDetailsUpdated`: Fired after the details in the sidebar have been (re-)rendered.
* Provides data about the selected portal.
* - `commDataAvailable`: Runs after data for any of the chats has been received and processed, but not yet
* been displayed. The data hash contains both the unprocessed raw ajax response as
* well as the chat data that is going to be used for display.
* - `publicChatDataAvailable`: Similar to `chatDataAvailable`, but for all chat only.
* - `factionChatDataAvailable`: Similar to `publicChatDataAvailable`, but for faction chat.
* - `alertsChatDataAvailable`: Similar to `publicChatDataAvailable`, but for alerts chat.
* - `requestFinished`: **Deprecated**. Recommended to use `mapDataRefreshEnd` instead.
* Called after each map data request is finished. Argument is {success: boolean}.
* - `iitcLoaded`: Called after IITC and all plugins have loaded.
* - `portalDetailLoaded`: Called when a request to load full portal detail completes.
* Parameters are guid, success, details.
* - `paneChanged`: Called when the current pane has changed. On desktop, this changes the current chat pane;
* on mobile, it also switches between map, info, and other panes defined by plugins.
* - `artifactsUpdated`: Called when the set of artifacts (including targets) has changed.
* Parameters are old, new.
* - `nicknameClicked`: Event triggered when a player's nickname is clicked.
* - `geoSearch`: Event triggered during a geographic search.
* - `search`: Event triggered during a search operation.
*
* @module hooks
*/
window._hooks = {};
window.VALID_HOOKS = []; // stub for compatibility
var isRunning = 0;
/**
* Executes all callbacks associated with a given hook event.
*
* @function runHooks
* @param {string} event - The name of the hook event.
* @param {Object} [data] - Additional data to pass to each callback.
* @returns {boolean} Returns `false` if the execution of the callbacks was interrupted, otherwise `true`.
*/
window.runHooks = function (event, data) {
if (!window._hooks[event]) {
return true;
}
var interrupted = false;
isRunning++;
$.each(window._hooks[event], function (ind, callback) {
try {
if (callback(data) === false) {
interrupted = true;
return false; // break from $.each
}
} catch (e) {
log.error('error running hook ' + event, '\n' + e, '\ncallback: ', callback, '\ndata: ', data);
}
});
isRunning--;
return !interrupted;
};
window.pluginCreateHook = function () {}; // stub for compatibility
/**
* Registers a callback function for a specified hook event.
*
* @function addHook
* @param {string} event - The name of the hook event.
* @param {Function} callback - The callback function to be executed when the event is triggered.
*/
window.addHook = function (event, callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function.');
}
if (!window._hooks[event]) {
window._hooks[event] = [callback];
} else {
window._hooks[event].push(callback);
}
};
/**
* Removes a previously registered callback function for a specified hook event.
* Callback must the SAME function to be unregistered.
*
* @function removeHook
* @param {string} event - The name of the hook event.
* @param {Function} callback - The callback function to be removed.
*/
window.removeHook = function (event, callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function.');
}
var listeners = window._hooks[event];
if (listeners) {
var index = listeners.indexOf(callback);
if (index === -1) {
log.warn("Callback wasn't registered for this event.");
} else {
if (isRunning) {
listeners[index] = $.noop;
window._hooks[event] = listeners = listeners.slice();
}
listeners.splice(index, 1);
}
}
};