Updated plugin API
- Renamed Settings to InternalSettings - Renamed getPlugins/getThemes to listPlugins/listThemes - Add Utils to the plugin API - Add CssUtils to the plugin API - Add error handling on plugin start - Changed ThemeManager.getConfigAsSCSS[Map] to accept a SettingsSet instead of an array of SettingsCategory objects - Add examples to the example plugin
This commit is contained in:
parent
3168012fde
commit
f9e278cc75
|
@ -72,9 +72,9 @@ export default class Plugin {
|
|||
|
||||
getSetting(setting_id, category_id) {
|
||||
for (let category of this.config) {
|
||||
if (category_id && category.category !== category_id) return;
|
||||
if (category_id && category.category !== category_id) continue;
|
||||
for (let setting of category.settings) {
|
||||
if (setting.id !== setting_id) return;
|
||||
if (setting.id !== setting_id) continue;
|
||||
return setting.value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,24 +8,64 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { ClientLogger as Logger, ClientIPC } from 'common';
|
||||
import Settings from './settings';
|
||||
import ExtModuleManager from './extmodulemanager';
|
||||
import PluginManager from './pluginmanager';
|
||||
import ThemeManager from './thememanager';
|
||||
import Events from './events';
|
||||
import { DOM } from 'ui';
|
||||
|
||||
class EventsWrapper {
|
||||
constructor(eventemitter) {
|
||||
this.__eventemitter = eventemitter;
|
||||
}
|
||||
|
||||
get eventSubs() {
|
||||
return this._eventSubs || (this._eventSubs = []);
|
||||
}
|
||||
|
||||
subscribe(event, callback) {
|
||||
if (this.eventSubs.find(e => e.event === event && e.callback === callback)) return;
|
||||
this.eventSubs.push({
|
||||
event,
|
||||
callback
|
||||
});
|
||||
this.__eventemitter.on(event, callback);
|
||||
}
|
||||
|
||||
unsubscribe(event, callback) {
|
||||
for (let index of this.eventSubs) {
|
||||
if (this.eventSubs[index].event !== event || (callback && this.eventSubs[index].callback === callback)) return;
|
||||
this.__eventemitter.off(event, this.eventSubs[index].callback);
|
||||
this.eventSubs.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
unsubscribeAll() {
|
||||
for (let event of this.eventSubs) {
|
||||
this.__eventemitter.off(event.event, event.callback);
|
||||
}
|
||||
this._eventSubs = [];
|
||||
}
|
||||
}
|
||||
|
||||
export default class PluginApi {
|
||||
|
||||
constructor(pluginInfo) {
|
||||
this.pluginInfo = pluginInfo;
|
||||
this.Events = new EventsWrapper(Events);
|
||||
}
|
||||
|
||||
loggerLog(message) { Logger.log(this.pluginInfo.name, message) }
|
||||
loggerErr(message) { Logger.err(this.pluginInfo.name, message) }
|
||||
loggerWarn(message) { Logger.warn(this.pluginInfo.name, message) }
|
||||
loggerInfo(message) { Logger.info(this.pluginInfo.name, message) }
|
||||
loggerDbg(message) { Logger.dbg(this.pluginInfo.name, message) }
|
||||
get plugin() {
|
||||
return PluginManager.getPluginById(this.pluginInfo.id || this.pluginInfo.name.toLowerCase().replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-'));
|
||||
}
|
||||
|
||||
loggerLog(...message) { Logger.log(this.pluginInfo.name, message) }
|
||||
loggerErr(...message) { Logger.err(this.pluginInfo.name, message) }
|
||||
loggerWarn(...message) { Logger.warn(this.pluginInfo.name, message) }
|
||||
loggerInfo(...message) { Logger.info(this.pluginInfo.name, message) }
|
||||
loggerDbg(...message) { Logger.dbg(this.pluginInfo.name, message) }
|
||||
get Logger() {
|
||||
return {
|
||||
log: this.loggerLog.bind(this),
|
||||
|
@ -36,44 +76,65 @@ export default class PluginApi {
|
|||
};
|
||||
}
|
||||
|
||||
get eventSubs() {
|
||||
return this._eventSubs || (this._eventSubs = []);
|
||||
}
|
||||
|
||||
eventSubscribe(event, callback) {
|
||||
if (this.eventSubs.find(e => e.event === event)) return;
|
||||
this.eventSubs.push({
|
||||
event,
|
||||
callback
|
||||
});
|
||||
Events.on(event, callback);
|
||||
}
|
||||
eventUnsubscribe(event) {
|
||||
const index = this.eventSubs.findIndex(e => e.event === event);
|
||||
if (index < 0) return;
|
||||
Events.off(event, this.eventSubs[0].callback);
|
||||
this.eventSubs.splice(index, 1);
|
||||
}
|
||||
eventUnsubscribeAll() {
|
||||
this.eventSubs.forEach(event => {
|
||||
Events.off(event.event, event.callback);
|
||||
});
|
||||
this._eventSubs = [];
|
||||
}
|
||||
get Events() {
|
||||
get Utils() {
|
||||
return {
|
||||
subscribe: this.eventSubscribe.bind(this),
|
||||
unsubscribe: this.eventUnsubscribe.bind(this),
|
||||
unsubscribeAll: this.eventUnsubscribeAll.bind(this)
|
||||
}
|
||||
overload: () => Utils.overload.apply(Utils, arguments),
|
||||
tryParseJson: () => Utils.tryParseJson.apply(Utils, arguments),
|
||||
toCamelCase: () => Utils.toCamelCase.apply(Utils, arguments),
|
||||
compare: () => Utils.compare.apply(Utils, arguments),
|
||||
deepclone: () => Utils.deepclone.apply(Utils, arguments)
|
||||
};
|
||||
}
|
||||
|
||||
getSetting(set, category, setting) {
|
||||
getInternalSetting(set, category, setting) {
|
||||
return Settings.get(set, category, setting);
|
||||
}
|
||||
get Settings() {
|
||||
get InternalSettings() {
|
||||
return {
|
||||
get: this.getSetting.bind(this)
|
||||
get: this.getInternalSetting.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
get injectedStyles() {
|
||||
return this._injectedStyles || (this._injectedStyles = []);
|
||||
}
|
||||
compileSass(scss, options) {
|
||||
return ClientIPC.send('bd-compileSass', Object.assign({ data: scss }, options));
|
||||
}
|
||||
getConfigAsSCSS(settingsset) {
|
||||
return ThemeManager.getConfigAsSCSS(settingsset ? settingsset : this.plugin.settings);
|
||||
}
|
||||
injectStyle(id, css) {
|
||||
if (id && !css) css = id, id = undefined;
|
||||
this.deleteStyle(id);
|
||||
const styleid = `plugin-${this.getPlugin().id}-${id}`;
|
||||
this.injectedStyles.push(styleid);
|
||||
DOM.injectStyle(css, styleid);
|
||||
}
|
||||
async injectSass(id, scss, options) {
|
||||
// In most cases a plugin's styles should be precompiled instead of using this
|
||||
if (id && !scss && !options) scss = id, id = undefined;
|
||||
const css = await this.compileSass(scss, options);
|
||||
this.injectStyle(id, css, options);
|
||||
}
|
||||
deleteStyle(id) {
|
||||
const styleid = `plugin-${this.getPlugin().id}-${id}`;
|
||||
this.injectedStyles.splice(this.injectedStyles.indexOf(styleid), 1);
|
||||
DOM.deleteStyle(styleid);
|
||||
}
|
||||
deleteAllStyles(id, css) {
|
||||
for (let id of this.injectedStyles) {
|
||||
this.deleteStyle(id);
|
||||
}
|
||||
}
|
||||
get CssUtils() {
|
||||
return {
|
||||
compileSass: this.compileSass.bind(this),
|
||||
getConfigAsSCSS: this.getConfigAsSCSS.bind(this),
|
||||
injectStyle: this.injectStyle.bind(this),
|
||||
injectSass: this.injectSass.bind(this),
|
||||
deleteStyle: this.deleteStyle.bind(this),
|
||||
deleteAllStyles: this.deleteAllStyles.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -81,13 +142,13 @@ export default class PluginApi {
|
|||
// This should require extra permissions
|
||||
return await PluginManager.waitForPlugin(plugin_id);
|
||||
}
|
||||
getPlugins(plugin_id) {
|
||||
listPlugins(plugin_id) {
|
||||
return PluginManager.localContent.map(plugin => plugin.id);
|
||||
}
|
||||
get Plugins() {
|
||||
return {
|
||||
getPlugin: this.getPlugin.bind(this),
|
||||
getPlugins: this.getPlugins.bind(this)
|
||||
listPlugins: this.listPlugins.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -95,13 +156,13 @@ export default class PluginApi {
|
|||
// This should require extra permissions
|
||||
return await ThemeManager.waitForContent(theme_id);
|
||||
}
|
||||
getThemes(plugin_id) {
|
||||
listThemes(plugin_id) {
|
||||
return ThemeManager.localContent.map(theme => theme.id);
|
||||
}
|
||||
get Themes() {
|
||||
return {
|
||||
getTheme: this.getTheme.bind(this),
|
||||
getThemes: this.getThemes.bind(this)
|
||||
getThemes: this.listThemes.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import Vendor from './vendor';
|
|||
import { ClientLogger as Logger } from 'common';
|
||||
import { Events, Permissions } from 'modules';
|
||||
import { Modals } from 'ui';
|
||||
import { ErrorEvent } from 'structs';
|
||||
|
||||
export default class extends ContentManager {
|
||||
|
||||
|
@ -37,10 +38,32 @@ export default class extends ContentManager {
|
|||
|
||||
static async loadAllPlugins(suppressErrors) {
|
||||
this.loaded = false;
|
||||
const loadAll = await this.loadAllContent(suppressErrors);
|
||||
const loadAll = await this.loadAllContent(true);
|
||||
this.loaded = true;
|
||||
for (let plugin of this.localPlugins) {
|
||||
if (plugin.enabled) plugin.start();
|
||||
try {
|
||||
if (plugin.enabled) plugin.start();
|
||||
} catch (err) {
|
||||
// Disable the plugin but don't save it - the next time BetterDiscord is started the plugin will attempt to start again
|
||||
plugin.userConfig.enabled = false;
|
||||
this.errors.push(new ErrorEvent({
|
||||
module: this.moduleName,
|
||||
message: `Failed to start ${plugin.name}`,
|
||||
err
|
||||
}));
|
||||
|
||||
Logger.err(this.moduleName, [`Failed to start plugin ${plugin.name}:`, err]);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.errors.length && !suppressErrors) {
|
||||
Modals.error({
|
||||
header: `${this.moduleName} - ${this.errors.length} ${this.contentType}${this.errors.length !== 1 ? 's' : ''} failed to load`,
|
||||
module: this.moduleName,
|
||||
type: 'err',
|
||||
content: this.errors
|
||||
});
|
||||
this._errors = [];
|
||||
}
|
||||
|
||||
return loadAll;
|
||||
|
|
|
@ -117,7 +117,7 @@ export default class Theme {
|
|||
|
||||
let css = '';
|
||||
if (this.info.type === 'sass') {
|
||||
const config = await ThemeManager.getConfigAsSCSS(this.config);
|
||||
const config = await ThemeManager.getConfigAsSCSS(this.settings);
|
||||
|
||||
css = await ClientIPC.send('bd-compileSass', {
|
||||
data: config,
|
||||
|
|
|
@ -72,10 +72,10 @@ export default class ThemeManager extends ContentManager {
|
|||
return theme instanceof Theme;
|
||||
}
|
||||
|
||||
static async getConfigAsSCSS(config) {
|
||||
static async getConfigAsSCSS(settingsset) {
|
||||
const variables = [];
|
||||
|
||||
for (let category of config) {
|
||||
for (let category of settingsset.categories) {
|
||||
for (let setting of category.settings) {
|
||||
const setting_scss = await this.parseSetting(setting);
|
||||
if (setting_scss) variables.push(`$${setting_scss[0]}: ${setting_scss[1]};`);
|
||||
|
@ -85,10 +85,10 @@ export default class ThemeManager extends ContentManager {
|
|||
return variables.join('\n');
|
||||
}
|
||||
|
||||
static async getConfigAsSCSSMap(config) {
|
||||
static async getConfigAsSCSSMap(settingsset) {
|
||||
const variables = [];
|
||||
|
||||
for (let category of config) {
|
||||
for (let category of settingsset.categories) {
|
||||
for (let setting of category.settings) {
|
||||
const setting_scss = await this.parseSetting(setting);
|
||||
if (setting_scss) variables.push(`${setting_scss[0]}: (${setting_scss[1]})`);
|
||||
|
|
|
@ -20,8 +20,6 @@ export default class ArraySetting extends Setting {
|
|||
constructor(args) {
|
||||
super(args);
|
||||
|
||||
console.log(this);
|
||||
|
||||
this.args.settings = this.settings.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)) : [];
|
||||
|
@ -101,7 +99,6 @@ export default class ArraySetting extends Setting {
|
|||
|
||||
updateValue(emit_multi = true, emit = true) {
|
||||
return this.__proto__.__proto__.setValue.call(this, this.items.map(item => {
|
||||
console.log('ArraySetting.updateValue:', item);
|
||||
if (!item) return;
|
||||
item.setSaved();
|
||||
return item.strip();
|
||||
|
@ -121,7 +118,7 @@ export default class ArraySetting extends Setting {
|
|||
async toSCSS() {
|
||||
const maps = [];
|
||||
for (let item of this.items)
|
||||
maps.push(await ThemeManager.getConfigAsSCSSMap(item.settings));
|
||||
maps.push(await ThemeManager.getConfigAsSCSSMap(item));
|
||||
|
||||
// Final comma ensures the variable is a list
|
||||
return maps.length ? maps.join(', ') + ',' : '()';
|
||||
|
|
|
@ -84,7 +84,7 @@ class DOMObserver {
|
|||
|
||||
}
|
||||
|
||||
class DOM {
|
||||
export default class DOM {
|
||||
|
||||
static get observer() {
|
||||
return this._observer || (this._observer = new DOMObserver());
|
||||
|
@ -123,7 +123,7 @@ class DOM {
|
|||
static deleteStyle(id) {
|
||||
const exists = this.getElement(`bd-styles > #${id}`);
|
||||
if (exists) exists.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static injectStyle(css, id) {
|
||||
this.deleteStyle(id);
|
||||
|
@ -153,5 +153,3 @@ class DOM {
|
|||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
export default DOM;
|
||||
|
|
|
@ -4,7 +4,7 @@ module.exports.default = {
|
|||
};
|
||||
|
||||
const component = {
|
||||
template: "<div style=\"margin-bottom: 15px; background-color: rgba(0, 0, 0, 0.2); border: 1px dashed rgba(255, 255, 255, 0.2); padding: 10px; color: #f6f6f7; font-weight: 500; font-size: 15px;\">Test custom setting {{ setting.id }}. This is included inline with the plugin/theme's config. (Which means it can't use any functions, but can still bind functions to events.) <button class=\"bd-button bd-button-primary\" style=\"display: inline-block; margin-left: 10px;\" @click=\"change(1)\">Set value to 1</button> <button class=\"bd-button bd-button-primary\" style=\"display: inline-block; margin-left: 10px;\" @click=\"change(2)\">Set value to 2</button></div>",
|
||||
template: "<div style=\"margin-bottom: 15px; background-color: rgba(0, 0, 0, 0.2); border: 1px dashed rgba(255, 255, 255, 0.2); padding: 10px; color: #f6f6f7; font-weight: 500; font-size: 15px;\">Test custom setting {{ setting.id }}. Also in component.js. It extends the CustomSetting class. <button class=\"bd-button bd-button-primary\" style=\"display: inline-block; margin-left: 10px;\" @click=\"change(1)\">Set value to 1</button> <button class=\"bd-button bd-button-primary\" style=\"display: inline-block; margin-left: 10px;\" @click=\"change(2)\">Set value to 2</button></div>",
|
||||
props: ['setting', 'change']
|
||||
};
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
module.exports = (Plugin, Api, Vendor, Dependencies) => {
|
||||
|
||||
const { $, moment, _ } = Vendor;
|
||||
const { Events, Logger } = Api;
|
||||
const { Events, Logger, InternalSettings, CssUtils } = Api;
|
||||
|
||||
return class extends Plugin {
|
||||
onStart() {
|
||||
async onStart() {
|
||||
await this.injectStyles();
|
||||
|
||||
Events.subscribe('TEST_EVENT', this.eventTest);
|
||||
Logger.log('onStart');
|
||||
|
||||
|
@ -16,7 +18,24 @@ module.exports = (Plugin, Api, Vendor, Dependencies) => {
|
|||
console.log('Received plugin settings update:', event);
|
||||
});
|
||||
|
||||
Logger.log(`Internal setting "core/default/test-setting" value: ${Api.Settings.get('core', 'default', 'test-setting')}`);
|
||||
this.settings.on('setting-updated', event => {
|
||||
Logger.log(`Setting ${event.category.id}/${event.setting.id} changed from ${event.old_value} to ${event.value}:`, event);
|
||||
});
|
||||
|
||||
// this.settings.categories.find(c => c.id === 'default').settings.find(s => s.id === 'default-5')
|
||||
this.settings.getSetting('default', 'default-0').on('setting-updated', async event => {
|
||||
Logger.log(`Some feature ${event.value ? 'enabled' : 'disabled'}`);
|
||||
});
|
||||
|
||||
this.settings.on('settings-updated', async event => {
|
||||
await this.injectStyles();
|
||||
|
||||
Logger.log('Settings updated:', event, 'Waiting before saving complete...');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
Logger.log('Done');
|
||||
});
|
||||
|
||||
Logger.log(`Internal setting "core/default/test-setting" value: ${InternalSettings.get('core', 'default', 'test-setting')}`);
|
||||
Events.subscribe('setting-updated', event => {
|
||||
console.log('Received internal setting update:', event);
|
||||
});
|
||||
|
@ -26,7 +45,24 @@ module.exports = (Plugin, Api, Vendor, Dependencies) => {
|
|||
return true;
|
||||
}
|
||||
|
||||
async injectStyles() {
|
||||
const scss = await CssUtils.getConfigAsSCSS() + `.layer-kosS71 .guilds-wrapper + * {
|
||||
&::before {
|
||||
content: 'Example plugin stuff (test radio setting #{$default-5} selected)';
|
||||
display: block;
|
||||
padding: 10px 40px;
|
||||
color: #eee;
|
||||
background-color: #202225;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
}`;
|
||||
Logger.log('Plugin SCSS:', scss);
|
||||
await CssUtils.injectSass(scss);
|
||||
}
|
||||
|
||||
onStop() {
|
||||
CssUtils.deleteAllStyles();
|
||||
Events.unsubscribeAll();
|
||||
Logger.log('onStop');
|
||||
console.log(this.showSettingsModal());
|
||||
|
|
Loading…
Reference in New Issue