BetterDiscordApp-v2/client/src/structs/settings/settingsset.js

458 lines
15 KiB
JavaScript

/**
* BetterDiscord Settings Set Struct
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
* All rights reserved.
* https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import SettingsCategory from './settingscategory';
import SettingsScheme from './settingsscheme';
import { ClientLogger as Logger, AsyncEventEmitter } from 'common';
import { SettingUpdatedEvent, SettingsUpdatedEvent } from 'structs';
import { Modals } from 'ui';
export default class SettingsSet {
constructor(args, ...merge) {
this.emitter = new AsyncEventEmitter();
this.args = args.args || args;
this.args.categories = this.categories.map(category => new SettingsCategory(category));
this.args.schemes = this.schemes.map(scheme => new SettingsScheme(scheme));
for (let newSet of merge) {
this._merge(newSet);
}
this.__settingUpdated = this.__settingUpdated.bind(this);
this.__settingsUpdated = this.__settingsUpdated.bind(this);
this.__addedSetting = this.__addedSetting.bind(this);
this.__removedSetting = this.__removedSetting.bind(this);
for (let category of this.categories) {
category.on('setting-updated', this.__settingUpdated);
category.on('settings-updated', this.__settingsUpdated);
category.on('added-setting', this.__addedSetting);
category.on('removed-setting', this.__removedSetting);
}
}
/**
* Set ID
*/
get id() {
return this.args.id;
}
/**
* Set name
*/
get text() {
return this.args.text;
}
/**
* Text to be displayed with the set.
*/
get headertext() {
return this.args.headertext || `${this.text} Settings`;
}
set headertext(headertext) {
this.args.headertext = headertext;
}
/**
* Whether this set should be displayed.
* Currently only used in the settings menu.
*/
get hidden() {
return this.args.hidden || false;
}
/**
* An array of SettingsCategory objects in this set.
*/
get categories() {
return this.args.categories || this.args.settings || [];
}
get settings() {
return this.categories;
}
/**
* An array of SettingsScheme objects that can be used in this set.
*/
get schemes() {
return this.args.schemes || [];
}
/**
* Whether any setting in this set has been changed.
*/
get changed() {
if (this.categories.find(category => category.changed)) return true;
return false;
}
/**
* Category event listeners.
* These only exists for use by the constructor and settingsset.addCategory.
*/
__settingUpdated({ category, setting, value, old_value }) {
return this.emit('setting-updated', new SettingUpdatedEvent({
set: this, set_id: this.id,
category, category_id: category.id,
setting, setting_id: setting.id,
value, old_value
}));
}
__settingsUpdated({ updatedSettings }) {
return this.emit('settings-updated', new SettingsUpdatedEvent({
updatedSettings: updatedSettings.map(updatedSetting => new SettingUpdatedEvent(Object.assign({
set: this, set_id: this.id
}, updatedSetting)))
}));
}
__addedSetting({ category, setting, at_index }) {
return this.emit('added-setting', {
set: this, set_id: this.id,
category, category_id: category.id,
setting, setting_id: setting.id,
at_index
});
}
__removedSetting({ category, setting, from_index }) {
return this.emit('removed-setting', {
set: this, set_id: this.id,
category, category_id: category.id,
setting, setting_id: setting.id,
from_index
});
}
/**
* Dynamically adds a category to this set.
* @param {SettingsCategory} category The category to add to this set
* @param {Number} index The index to add the category at (optional)
* @return {Promise}
*/
async addCategory(category, index) {
if (this.categories.find(c => c === category)) return;
if (!(category instanceof SettingsCategory))
category = new SettingsCategory(category);
if (this.getCategory(category.id))
throw {message: 'A category with this ID already exists.'};
category.on('setting-updated', this.__settingUpdated);
category.on('settings-updated', this.__settingsUpdated);
category.on('added-setting', this.__addedSetting);
category.on('removed-setting', this.__removedSetting);
if (index === undefined) index = this.categories.length;
this.categories.splice(index, 0, category);
const event = {
set: this, set_id: this.id,
category, category_id: category.id,
at_index: index
};
await category.emit('added-to', event);
await this.emit('added-category', event);
return category;
}
/**
* Dynamically removes a category from this set.
* @param {SettingsCategory} category The category to remove from this set
* @return {Promise}
*/
async removeCategory(category) {
category.off('setting-updated', this.__settingUpdated);
category.off('settings-updated', this.__settingsUpdated);
category.off('added-setting', this.__addedSetting);
category.off('removed-setting', this.__removedSetting);
let index;
while ((index = this.categories.findIndex(c => c === category)) > -1) {
this.categories.splice(index, 0);
}
const event = {
set: this, set_id: this.id,
category, category_id: category.id,
from_index: index
};
await category.emit('removed-from', event);
await this.emit('removed-category', event);
}
/**
* Dynamically adds a scheme to this set.
* @param {SettingsScheme} scheme The scheme to add to this set
* @param {Number} index The index to add the scheme at (optional)
* @return {Promise}
*/
async addScheme(scheme, index) {
if (this.schemes.find(c => c === scheme)) return;
if (!(scheme instanceof SettingsScheme))
scheme = new SettingsScheme(scheme);
if (this.schemes.find(s => s.id === scheme.id))
throw {message: 'A scheme with this ID already exists.'};
if (index === undefined) index = this.schemes.length;
this.schemes.splice(index, 0, scheme);
await this.emit('added-scheme', {
set: this, set_id: this.id,
scheme, scheme_id: scheme.id,
at_index: index
});
return scheme;
}
/**
* Dynamically removes a scheme from this set.
* @param {SettingsScheme} scheme The scheme to remove from this set
* @return {Promise}
*/
async removeScheme(scheme) {
let index;
while ((index = this.schemes.findIndex(s => s === scheme)) > -1) {
this.schemes.splice(index, 0);
}
await this.emit('removed-scheme', {
set: this, set_id: this.id,
scheme, scheme_id: scheme.id,
from_index: index
});
}
/**
* Returns the first category where calling {function} returns true.
* @param {Function} function A function to call to filter categories
* @return {SettingsCategory}
*/
find(f) {
return this.categories.find(f);
}
/**
* Returns all categories where calling {function} returns true.
* @param {Function} function A function to call to filter categories
* @return {Array} An array of matching SettingsCategory objects
*/
findCategories(f) {
return this.categories.filter(f);
}
/**
* Returns the category with the ID {id}.
* @param {String} id The ID of the category to look for
* @return {SettingsCategory}
*/
getCategory(id) {
return this.find(category => category.id === id);
}
/**
* Returns the first setting where calling {function} returns true.
* @param {Function} function A function to call to filter settings
* @return {Setting}
*/
findSetting(f) {
for (let category of this.categories) {
const setting = category.find(f);
if (setting) return setting;
}
}
/**
* Returns all settings where calling {function} returns true.
* @param {Function} function A function to call to filter settings
* @return {Array} An array of matching Setting objects
*/
findSettings(f) {
return this.findSettingsInCategory(() => true, f);
}
/**
* Returns the first setting where calling {function} returns true.
* @param {Function} categoryFunction A function to call to filter categories
* @param {Function} function A function to call to filter settings
* @return {Array} An array of matching Setting objects
*/
findSettingInCategory(cf, f) {
for (let category of this.categories.filter(cf)) {
const setting = category.find(f);
if (setting) return setting;
}
}
/**
* Returns all settings where calling {function} returns true.
* @param {Function} categoryFunction A function to call to filter categories
* @param {Function} function A function to call to filter settings
* @return {Array} An array of matching Setting objects
*/
findSettingsInCategory(cf, f) {
let settings = [];
for (let category of this.categories.filter(cf)) {
settings = settings.concat(category.findSettings(f));
}
return settings;
}
/**
* Returns the setting with the ID {id}.
* @param {String} categoryid The ID of the category to look in (optional)
* @param {String} id The ID of the setting to look for
* @return {Setting}
*/
getSetting(id, sid) {
if (sid) return this.findSettingInCategory(category => category.id === id, setting => setting.id === sid);
return this.findSetting(setting => setting.id === id);
}
/**
* Returns the value of the setting with the ID {id}.
* @param {String} categoryid The ID of the category to look in (optional)
* @param {String} id The ID of the setting to look for
* @return {Any}
*/
get(cid, sid) {
const setting = this.getSetting(cid, sid);
return setting ? setting.value : undefined;
}
/**
* Opens this set in a modal.
* @param {String} headertext Text to be displayed in the modal header
* @param {Object} options Additional options to pass to Modals.settings
* @return {Modal}
*/
showModal(headertext, options) {
return Modals.settings(this, headertext ? headertext : this.headertext, options);
}
/**
* Merges a set into this set without emitting events (and therefore synchronously).
* This only exists for use by the constructor.
*/
_merge(newSet, emit_multi = true) {
let updatedSettings = [];
// const categories = newSet instanceof Array ? newSet : newSet.settings;
const categories = newSet && newSet.args ? newSet.args.settings : newSet ? newSet.settings : newSet;
if (!categories) return [];
for (let newCategory of categories) {
const category = this.find(category => category.category === newCategory.category);
if (!category) {
Logger.warn('SettingsCategory', `Trying to merge category ${newCategory.id}, which does not exist.`);
continue;
}
const updatedSetting = category._merge(newCategory, false);
if (!updatedSetting) continue;
updatedSettings = updatedSettings.concat(updatedSetting.map(({ category, setting, value, old_value }) => ({
set: this, set_id: this.id,
category, category_id: category.id,
setting, setting_id: setting.id,
value, old_value
})));
}
return updatedSettings;
}
/**
* Merges another set into this set.
* @param {SettingsSet} newSet The set to merge into this set
* @return {Promise}
*/
async merge(newSet, emit_multi = true) {
let updatedSettings = [];
// const categories = newSet instanceof Array ? newSet : newSet.settings;
const categories = newSet && newSet.args ? newSet.args.settings : newSet ? newSet.settings : newSet;
if (!categories) return [];
for (let newCategory of categories) {
const category = this.find(category => category.category === newCategory.category);
if (!category) {
Logger.warn('SettingsCategory', `Trying to merge category ${newCategory.id}, which does not exist.`);
continue;
}
const updatedSetting = await category.merge(newCategory, false);
if (!updatedSetting) continue;
updatedSettings = updatedSettings.concat(updatedSetting.map(({ category, setting, value, old_value }) => ({
set: this, set_id: this.id,
category, category_id: category.id,
setting, setting_id: setting.id,
value, old_value
})));
}
if (emit_multi)
await this.emit('settings-updated', new SettingsUpdatedEvent({
updatedSettings
}));
return updatedSettings;
}
/**
* Marks all settings in this set as saved (not changed).
*/
setSaved() {
for (let category of this.categories) {
category.setSaved();
}
}
/**
* Returns an object that can be stored as JSON and later merged back into a set with settingsset.merge.
* @return {Object}
*/
strip() {
const stripped = {};
if (this.id) stripped.id = this.id;
stripped.settings = this.categories.map(category => category.strip());
return stripped;
}
/**
* Returns a copy of this set that can be changed and then merged back into a set with settingsset.merge.
* @param {SettingsSet} ...merge A set to merge into the new set
* @return {SettingsSet}
*/
clone(...merge) {
return new SettingsSet({
id: this.id,
text: this.text,
headertext: this.headertext,
settings: this.categories.map(category => category.clone()),
schemes: this.schemes
}, ...merge);
}
on(...args) { return this.emitter.on(...args); }
off(...args) { return this.emitter.removeListener(...args); }
emit(...args) { return this.emitter.emit(...args); }
}