269 lines
8.1 KiB
JavaScript
269 lines
8.1 KiB
JavaScript
/**
|
|
* BetterDiscord Array Setting 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 { ThemeManager } from 'modules';
|
|
import { Utils } from 'common';
|
|
import Setting from './basesetting';
|
|
import SettingsSet from '../settingsset';
|
|
import SettingsCategory from '../settingscategory';
|
|
import SettingsScheme from '../settingsscheme';
|
|
import { SettingsUpdatedEvent } from 'structs';
|
|
|
|
export default class ArraySetting extends Setting {
|
|
|
|
constructor(args, ...merge) {
|
|
super(args, ...merge);
|
|
|
|
this.args.categories = this.categories.map(category => new SettingsCategory(category));
|
|
this.args.schemes = this.schemes.map(scheme => new SettingsScheme(scheme));
|
|
this.args.items = this.value ? this.value.map(item => this.createItem(item.args || item)) : [];
|
|
|
|
this._setValue(this.getValue());
|
|
}
|
|
|
|
/**
|
|
* The value to use when the setting doesn't have a value.
|
|
*/
|
|
get defaultValue() {
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* An array of sets currently in this array setting.
|
|
*/
|
|
get items() {
|
|
return this.args.items || [];
|
|
}
|
|
|
|
set items(items) {
|
|
this.args.items = items ? items.map(item => this.createItem(item)) : [];
|
|
this.updateValue();
|
|
}
|
|
|
|
/**
|
|
* Whether the setting should take the full width of the settings panel.
|
|
* This is always false for array settings.
|
|
*/
|
|
get fullwidth() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* An array of SettingsCategory objects that each set in this setting should have.
|
|
*/
|
|
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 array's sets.
|
|
*/
|
|
get schemes() {
|
|
return this.args.schemes || [];
|
|
}
|
|
|
|
/**
|
|
* Whether to display this array setting's sets inline instead of opening them in a modal.
|
|
*/
|
|
get inline() {
|
|
return this.args.inline || false;
|
|
}
|
|
|
|
/**
|
|
* Whether to allow opening this array setting's sets in a modal.
|
|
* This is always true when inline is false.
|
|
*/
|
|
get allow_external() {
|
|
return this.args.allow_external || !this.inline;
|
|
}
|
|
|
|
/**
|
|
* The minimum amount of sets the user may create.
|
|
* This only restricts deleting sets when there is less or equal sets than this, and does not ensure that this number of items actually exists.
|
|
*/
|
|
get min() {
|
|
return this.args.min || 0;
|
|
}
|
|
|
|
/**
|
|
* The maximum amount of sets the user may create.
|
|
*/
|
|
get max() {
|
|
return this.args.max || null;
|
|
}
|
|
|
|
/**
|
|
* Adds a new set to this array setting.
|
|
* This ignores the maximum value.
|
|
* @param {SettingsSet} item Values to merge into the new set (optional)
|
|
* @return {SettingsSet} The new set
|
|
*/
|
|
async addItem(_item) {
|
|
const item = this.createItem(_item);
|
|
this.args.items.push(item);
|
|
await this.updateValue();
|
|
|
|
await this.emit('item-added', { item });
|
|
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Removes a set from this array setting.
|
|
* This ignores the minimum value.
|
|
* @param {SettingsSet} item The set to remove
|
|
* @return {Promise}
|
|
*/
|
|
async removeItem(item) {
|
|
this.args.items = this.items.filter(i => i !== item);
|
|
await this.updateValue();
|
|
|
|
await this.emit('item-removed', { item });
|
|
}
|
|
|
|
/**
|
|
* Creates a new set for this array setting.
|
|
* @param {SettingsSet} item Values to merge into the new set (optional)
|
|
* @return {SettingsSet} The new set
|
|
*/
|
|
createItem(item) {
|
|
if (item instanceof SettingsSet)
|
|
return item;
|
|
|
|
const set = new SettingsSet({
|
|
id: item ? item.args ? item.args.id : item.id : Math.random(),
|
|
categories: this.categories.map(c => c.clone()),
|
|
schemes: this.schemes
|
|
}, item ? item.args || item : undefined);
|
|
|
|
set.setSaved();
|
|
set.on('settings-updated', async event => {
|
|
await this.emit('item-updated', { item: set, event, updatedSettings: event.updatedSettings });
|
|
if (event.args.updating_array !== this) await this.updateValue();
|
|
});
|
|
return set;
|
|
}
|
|
|
|
/**
|
|
* Function to be called after the value changes.
|
|
* This can be overridden by other settings types.
|
|
* This function is used when the value needs to be updated synchronously (basically just in the constructor - so there won't be any events to emit anyway).
|
|
* @param {SettingUpdatedEvent} updatedSetting
|
|
*/
|
|
setValueHookSync(updatedSetting) {
|
|
this.args.items = updatedSetting.value ? updatedSetting.value.map(item => this.createItem(item)) : [];
|
|
}
|
|
|
|
/**
|
|
* Function to be called after the value changes.
|
|
* This can be overridden by other settings types.
|
|
* @param {SettingUpdatedEvent} updatedSetting
|
|
*/
|
|
async setValueHook(updatedSetting) {
|
|
const newItems = [];
|
|
let error;
|
|
|
|
for (const newItem of updatedSetting.value) {
|
|
try {
|
|
const item = this.items.find(i => i.id && i.id === newItem.id);
|
|
|
|
if (item) {
|
|
// Merge the new item into the original item
|
|
newItems.push(item);
|
|
const updatedSettings = await item.merge(newItem, false);
|
|
if (!updatedSettings.length) continue;
|
|
|
|
const event = new SettingsUpdatedEvent({
|
|
updatedSettings,
|
|
updating_array: this
|
|
});
|
|
|
|
await item.emit('settings-updated', event);
|
|
// await this.emit('item-updated', { item, event, updatedSettings });
|
|
} else {
|
|
// Add a new item
|
|
const item = this.createItem(newItem);
|
|
newItems.push(item);
|
|
await this.emit('item-added', { item });
|
|
}
|
|
} catch (e) { error = e; }
|
|
}
|
|
|
|
for (const item of this.items) {
|
|
if (newItems.includes(item)) continue;
|
|
|
|
try {
|
|
// Item removed
|
|
await this.emit('item-removed', { item });
|
|
} catch (e) { error = e; }
|
|
}
|
|
|
|
this.args.items = newItems;
|
|
|
|
// We can't throw anything before the items array is updated, otherwise the array setting would be in an inconsistent state where the values in this.items wouldn't match the values in this.value
|
|
if (error) throw error;
|
|
}
|
|
|
|
/**
|
|
* Updates the value of this array setting.
|
|
* This only exists for use by array settings.
|
|
* @return {Promise}
|
|
*/
|
|
getValue() {
|
|
return this.items.map(item => {
|
|
if (!item) return;
|
|
item.setSaved();
|
|
return item.strip();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Updates the value of this array setting.
|
|
* This only exists for use by array settings.
|
|
* @return {Promise}
|
|
*/
|
|
updateValue(emit_multi = true, emit = true) {
|
|
return this.setValue(this.getValue(), emit_multi, emit);
|
|
}
|
|
|
|
/**
|
|
* Sets the path of the plugin/theme this setting is part of.
|
|
* This is passed to this array setting's settings.
|
|
* @param {String} contentPath The plugin/theme's directory path
|
|
*/
|
|
setContentPath(contentPath) {
|
|
this.args.path = contentPath;
|
|
|
|
for (const category of this.categories) {
|
|
for (const setting of category.settings) {
|
|
setting.setContentPath(contentPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a representation of this setting's value in SCSS.
|
|
* @return {Promise}
|
|
*/
|
|
async toSCSS() {
|
|
const maps = [];
|
|
for (const item of this.items)
|
|
maps.push(await ThemeManager.getConfigAsSCSSMap(item));
|
|
|
|
// Final comma ensures the variable is a list
|
|
return maps.length ? `${maps.join(', ')},` : '()';
|
|
}
|
|
|
|
}
|