curate/src/structures/MultiSelect.js
2020-08-10 23:08:26 -05:00

145 lines
No EOL
5.2 KiB
JavaScript

const EventEmitter = require('eventemitter3');
const GenericPager = require('./GenericPager');
const Paginator = require('./Paginator');
const lodash = require('lodash');
/**
* A prompt that allows users to toggle multiple values
*/
class MultiSelect extends EventEmitter {
/**
* @param {TrelloBot} client The client to use
* @param {Message} message The user's message to read permissions from
* @param {Object} options The options for the multi-select
* @param {string|Array<string>} options.path The path of the boolean
* @param {string} [options.checkEmoji] The emoji that resembles true
* @param {string} [options.uncheckEmoji] The emoji that resembles false
* @param {Object} pagerOptions The options for the pager
*/
constructor(client, message, { path, checkEmoji = '☑️', uncheckEmoji = '⬜' }, pagerOptions = {}) {
super();
this.client = client;
this.message = message;
this.pagerOptions = pagerOptions;
this.displayFunc = pagerOptions.display || ((item) => item.toString());
this.boolPath = path;
// Override some pager options
this.pagerOptions.display = (item, i, ai) => {
const value = lodash.get(item, this.boolPath);
return `\`[${ai + 1}]\` ${value ? checkEmoji : uncheckEmoji} ${this.displayFunc(item, i, ai)}`;
};
this.pagerOptions.header = pagerOptions.header || 'Type the number of the item to toggle its value.';
this.pagerOptions.footer = (pagerOptions.footer ? pagerOptions.footer + '\n\n' : '') +
'Type "save" to save the selection, otherwise type "cancel" to exit.';
this.pagerOptions.embedExtra = this.pagerOptions.embedExtra || {};
this.pagerOptions.embedExtra.author = {
name: `${message.author.username}#${message.author.discriminator}`,
icon_url: message.author.avatarURL || message.author.defaultAvatarURL
};
this.pager = new GenericPager(client, message, this.pagerOptions);
this.halt = null;
}
/**
* Starts the prompt
* @param {string} channelID The channel to post the new message to
* @param {string} userID The user's ID that started the process
* @param {number} timeout
*/
async start(channelID, userID, timeout) {
if (this.pager.items.length === 0)
return null;
await this.pager.start(channelID, userID, timeout);
// React with done
if (this.pager.collector)
await this.pager.message.addReaction(MultiSelect.DONE);
this.halt = this.client.messageAwaiter.createHalt(channelID, userID, timeout);
// Sync timeouts
if (this.pager.collector)
this.pager.collector.restart();
this.halt.restart();
return new Promise(resolve => {
let result = null;
this.halt.on('message', nextMessage => {
if (this.pager.canManage())
nextMessage.delete();
if (MultiSelect.CANCEL_TRIGGERS.includes(nextMessage.content.toLowerCase())) {
result = { _canceled: true };
this.halt.end();
} else if (MultiSelect.DONE_TRIGGERS.includes(nextMessage.content.toLowerCase())) {
result = this.pager.items;
this.halt.end();
}
// Find and update item
const chosenIndex = parseInt(nextMessage.content);
if (chosenIndex <= 0) return;
let chosenItem = this.pager.items[chosenIndex - 1];
if (chosenItem !== undefined) {
const oldItem = chosenItem;
chosenItem = lodash.set(chosenItem, this.boolPath, !lodash.get(chosenItem, this.boolPath));
this.emit('update', oldItem, chosenItem, chosenIndex - 1);
this.pager.items.splice(chosenIndex - 1, 1, chosenItem);
this.pager.updateMessage();
}
this.halt.restart();
if (this.pager.collector)
this.pager.collector.restart();
});
this.halt.on('end', () => {
// In case the halt ends before reactions are finished coming up
this.pager.reactionsCleared = true;
if (this.pager.collector)
this.pager.collector.end();
this.pager.message.delete().catch(() => {});
if (result && result._canceled) {
this.pager.message.channel.createMessage(
`<@${userID}>, Your last prompt was canceled. All changes have been lost.`);
result = null;
} else if (result === null)
this.pager.message.channel.createMessage(
`<@${userID}>, Your last prompt was timed out. All changes have been lost.`);
resolve(result);
});
if (this.pager.collector) {
this.pager.collector.on('clearReactions', () => {
if (!this.pager.canManage())
this.pager.message.removeReaction(MultiSelect.DONE).catch(() => {});
});
this.pager.collector.on('reaction', emoji => {
if (Paginator.STOP === emoji.name) {
result = { _canceled: true };
this.halt.end();
} else if (MultiSelect.DONE === emoji.name) {
result = this.pager.items;
this.halt.end();
}
this.halt.restart();
});
}
});
}
}
MultiSelect.DONE = '✅';
MultiSelect.CANCEL_TRIGGERS = [
'cancel', 'stop', 'quit'
];
MultiSelect.DONE_TRIGGERS = [
'save', 'finish', 'done'
];
module.exports = MultiSelect;