Added asynceventemitter.once and add comments
This commit is contained in:
parent
26404843c2
commit
5373512d9b
|
@ -8,18 +8,22 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { Utils, FileUtils } from 'common';
|
||||
import { Utils, FileUtils, AsyncEventEmitter } from 'common';
|
||||
import { Settings, Events, PluginManager, ThemeManager } from 'modules';
|
||||
import BaseModal from './components/common/Modal.vue';
|
||||
import BasicModal from './components/bd/modals/BasicModal.vue';
|
||||
import ConfirmModal from './components/bd/modals/ConfirmModal.vue';
|
||||
import ErrorModal from './components/bd/modals/ErrorModal.vue';
|
||||
import SettingsModal from './components/bd/modals/SettingsModal.vue';
|
||||
import PermissionModal from './components/bd/modals/PermissionModal.vue';
|
||||
|
||||
export default class {
|
||||
class Modal extends AsyncEventEmitter {
|
||||
constructor(_modal, component) {
|
||||
super();
|
||||
Object.assign(this, _modal);
|
||||
|
||||
static add(modal, component) {
|
||||
modal.component = modal.component || {
|
||||
const modal = this;
|
||||
this.component = this.component || {
|
||||
template: '<custom-modal :modal="modal" />',
|
||||
components: { 'custom-modal': component },
|
||||
data() { return { modal }; },
|
||||
|
@ -28,49 +32,112 @@ export default class {
|
|||
modal.vue = this.$children[0];
|
||||
}
|
||||
};
|
||||
modal.closing = false;
|
||||
modal.close = force => this.close(modal, force);
|
||||
modal.id = Date.now();
|
||||
|
||||
this.closing = false;
|
||||
this.id = Date.now();
|
||||
this.vueInstance = undefined;
|
||||
this.vue = undefined;
|
||||
|
||||
this.closed = this.once('closed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the modal and removes it from the stack.
|
||||
* @param {Boolean} force If not true throwing an error in the close hook will stop the modal being closed
|
||||
* @return {Promise}
|
||||
*/
|
||||
close(force) {
|
||||
return Modals.close(this, force);
|
||||
}
|
||||
}
|
||||
|
||||
export default class Modals {
|
||||
|
||||
/**
|
||||
* Adds a modal to the open stack.
|
||||
* @param {Object} modal A Modal object or options used to create a Modal object
|
||||
* @param {Object} component A Vue component that will be used to render the modal (optional if modal is a Modal object or it contains a component property)
|
||||
* @return {Modal} The Modal object that was passed or created using the passed options
|
||||
*/
|
||||
static add(_modal, component) {
|
||||
const modal = _modal instanceof Modal ? _modal : new Modal(_modal, component);
|
||||
|
||||
this.stack.push(modal);
|
||||
Events.emit('bd-refresh-modals');
|
||||
return modal;
|
||||
}
|
||||
|
||||
static close(modal, force) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
/**
|
||||
* Closes a modal and removes it from the stack.
|
||||
* @param {Modal} modal The modal to close
|
||||
* @param {Boolean} force If not true throwing an error in the close hook will stop the modal being closed
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async close(modal, force) {
|
||||
try {
|
||||
if (modal.beforeClose) {
|
||||
try {
|
||||
const beforeCloseResult = await modal.beforeClose(force);
|
||||
if (beforeCloseResult && !force) return reject(beforeCloseResult);
|
||||
} catch (err) {
|
||||
if (!force) return reject(err);
|
||||
}
|
||||
const beforeCloseResult = await modal.beforeClose(force);
|
||||
if (beforeCloseResult) throw beforeCloseResult;
|
||||
}
|
||||
await modal.emit('close', force);
|
||||
} catch (err) {
|
||||
Logger.err('Modals', ['Error thrown in modal close event:', err]);
|
||||
if (!force) throw err;
|
||||
}
|
||||
|
||||
modal.closing = true;
|
||||
setTimeout(() => {
|
||||
this._stack = this.stack.filter(m => m !== modal);
|
||||
Events.emit('bd-refresh-modals');
|
||||
resolve();
|
||||
}, 200);
|
||||
});
|
||||
modal.closing = true;
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
|
||||
this._stack = this.stack.filter(m => m !== modal);
|
||||
Events.emit('bd-refresh-modals');
|
||||
|
||||
try {
|
||||
await modal.emit('closed', force);
|
||||
} catch (err) {
|
||||
Logger.err('Modals', ['Error thrown in modal closed event:', err]);
|
||||
if (!force) throw err;
|
||||
}
|
||||
}
|
||||
|
||||
static closeAll() {
|
||||
/**
|
||||
* Closes all open modals and removes them from the stack.
|
||||
* @param {Boolean} force If not true throwing an error in the close hook will stop that modal and any modals higher in the stack from being closed
|
||||
* @return {Promise}
|
||||
*/
|
||||
static closeAll(force) {
|
||||
const promises = [];
|
||||
for (let modal of this.stack)
|
||||
modal.close();
|
||||
promises.push(modal.close(force));
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
static closeLast() {
|
||||
if (!this.stack.length) return;
|
||||
this.stack[this.stack.length - 1].close();
|
||||
/**
|
||||
* Closes highest modal in the stack and removes it from the stack.
|
||||
* @param {Boolean} force If not true throwing an error in the close hook will stop the modal being closed
|
||||
* @return {Promise}
|
||||
*/
|
||||
static closeLast(force) {
|
||||
if (!this.stack.length) return Promise.resolve();
|
||||
return this.stack[this.stack.length - 1].close(force);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new basic modal and adds it to the open stack.
|
||||
* @param {String} title A string that will be displayed in the modal header
|
||||
* @param {String} text A string that will be displayed in the modal body
|
||||
* @return {Modal}
|
||||
*/
|
||||
static basic(title, text) {
|
||||
return this.add({ title, text }, BasicModal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new confirm modal and adds it to the open stack.
|
||||
* The modal will have a promise property that will be set to a Promise object that is resolved or rejected if the user clicks the confirm button or closes the modal.
|
||||
* @param {String} title A string that will be displayed in the modal header
|
||||
* @param {String} text A string that will be displayed in the modal body
|
||||
* @return {Modal}
|
||||
*/
|
||||
static confirm(title, text) {
|
||||
const modal = { title, text };
|
||||
modal.promise = new Promise((resolve, reject) => {
|
||||
|
@ -81,6 +148,14 @@ export default class {
|
|||
return modal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new permissions modal and adds it to the open stack.
|
||||
* The modal will have a promise property that will be set to a Promise object that is resolved or rejected if the user accepts the permissions or closes the modal.
|
||||
* @param {String} title A string that will be displayed in the modal header
|
||||
* @param {String} name The requesting plugin's name
|
||||
* @param {Array} perms The permissions the plugin is requesting
|
||||
* @return {Modal}
|
||||
*/
|
||||
static permissions(title, name, perms) {
|
||||
const modal = { title, name, perms };
|
||||
modal.promise = new Promise((resolve, reject) => {
|
||||
|
@ -91,10 +166,20 @@ export default class {
|
|||
return modal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new error modal and adds it to the open stack.
|
||||
* @param {Object} event An object containing details about the error[s] to display
|
||||
* @return {Modal}
|
||||
*/
|
||||
static error(event) {
|
||||
return this.add({ event }, ErrorModal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new error modal with errors from PluginManager and ThemeManager and adds it to the open stack.
|
||||
* @param {Boolean} clear Whether to clear the errors array after opening the modal
|
||||
* @return {Modal}
|
||||
*/
|
||||
static showContentManagerErrors(clear = true) {
|
||||
// Get any errors from PluginManager and ThemeManager
|
||||
const errors = ([]).concat(PluginManager.errors).concat(ThemeManager.errors);
|
||||
|
@ -122,6 +207,13 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new settings modal and adds it to the open stack.
|
||||
* @param {SettingsSet} settingsset The SettingsSet object to [clone and] display in the modal
|
||||
* @param {String} headertext A string that will be displayed in the modal header
|
||||
* @param {Object} options Additional options that will be passed to the modal
|
||||
* @return {Modal}
|
||||
*/
|
||||
static settings(settingsset, headertext, options) {
|
||||
return this.add(Object.assign({
|
||||
headertext: headertext ? headertext : settingsset.headertext,
|
||||
|
@ -130,18 +222,40 @@ export default class {
|
|||
}, options), SettingsModal);
|
||||
}
|
||||
|
||||
static internalSettings(set_id) {
|
||||
/**
|
||||
* Creates a new settings modal with one of BetterDiscord's settings sets and adds it to the open stack.
|
||||
* @param {SettingsSet} set_id The ID of the SettingsSet object to [clone and] display in the modal
|
||||
* @param {String} headertext A string that will be displayed in the modal header
|
||||
* @return {Modal}
|
||||
*/
|
||||
static internalSettings(set_id, headertext) {
|
||||
const set = Settings.getSet(set_id);
|
||||
if (!set) return;
|
||||
return this.settings(set, set.headertext);
|
||||
return this.settings(set, headertext);
|
||||
}
|
||||
|
||||
static contentSettings(content) {
|
||||
return this.settings(content.settings, content.name + ' Settings');
|
||||
/**
|
||||
* Creates a new settings modal with a plugin/theme's settings set and adds it to the open stack.
|
||||
* @param {SettingsSet} content The plugin/theme whose settings set is to be [cloned and] displayed in the modal
|
||||
* @param {String} headertext A string that will be displayed in the modal header
|
||||
* @return {Modal}
|
||||
*/
|
||||
static contentSettings(content, headertext) {
|
||||
return this.settings(content.settings, headertext ? headertext : content.name + ' Settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of open modals.
|
||||
*/
|
||||
static get stack() {
|
||||
return this._stack ? this._stack : (this._stack = []);
|
||||
}
|
||||
|
||||
/**
|
||||
* A base Vue component for modals to use.
|
||||
*/
|
||||
static get baseComponent() {
|
||||
return BaseModal;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,32 +10,51 @@
|
|||
|
||||
import EventEmitter from 'events';
|
||||
|
||||
/**
|
||||
* Extends Node.js' EventEmitter to trigger event listeners asyncronously.
|
||||
*/
|
||||
export default class AsyncEventEmitter extends EventEmitter {
|
||||
|
||||
emit(event, ...data) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let listeners = this._events[event] || [];
|
||||
listeners = Array.isArray(listeners) ? listeners : [listeners];
|
||||
/**
|
||||
* Emits an event.
|
||||
* @param {String} event The event to emit
|
||||
* @param {Any} ...data Data to be passed to event listeners
|
||||
* @return {Promise}
|
||||
*/
|
||||
async emit(event, ...data) {
|
||||
let listeners = this._events[event] || [];
|
||||
listeners = Array.isArray(listeners) ? listeners : [listeners];
|
||||
|
||||
// Special treatment of internal newListener and removeListener events
|
||||
if(event === 'newListener' || event === 'removeListener') {
|
||||
data = [{
|
||||
event: data,
|
||||
fn: err => {
|
||||
if (err) throw err;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
for (let listener of listeners) {
|
||||
try {
|
||||
await listener.call(this, ...data);
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
// Special treatment of internal newListener and removeListener events
|
||||
if(event === 'newListener' || event === 'removeListener') {
|
||||
data = [{
|
||||
event: data,
|
||||
fn: err => {
|
||||
if (err) throw err;
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
resolve();
|
||||
for (let listener of listeners) {
|
||||
await listener.apply(this, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event listener that will be removed when it is called and therefore only be called once.
|
||||
* If a callback is not specified a promise that is resolved once the event is triggered is returned.
|
||||
*/
|
||||
once(event, callback) {
|
||||
if (callback) {
|
||||
// If a callback was specified add this event as normal
|
||||
return EventEmitter.prototype.once.apply(this, arguments);
|
||||
}
|
||||
|
||||
// Otherwise return a promise that is resolved once this event is triggered
|
||||
return new Promise((resolve, reject) => {
|
||||
EventEmitter.prototype.once.call(this, event, data => {
|
||||
return resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue