Add plugin settings events

- Moved the Theme class to it’s own file
This commit is contained in:
Samuel Elliott 2018-02-13 23:23:52 +00:00
parent d3c3de5a79
commit 08271deea1
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
10 changed files with 278 additions and 146 deletions

View File

@ -29,7 +29,7 @@ export default class {
return this._contentPath ? this._contentPath : (this._contentPath = Globals.getObject('paths').find(path => path.id === this.pathId).path);
}
static async loadAllContent(supressErrors) {
static async loadAllContent(suppressErrors) {
try {
await FileUtils.ensureDirectory(this.contentPath);
const directories = await FileUtils.listDirectory(this.contentPath);
@ -48,7 +48,7 @@ export default class {
}
}
if (this.errors.length && !supressErrors) {
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,
@ -154,7 +154,7 @@ export default class {
mainPath
}
const content = await this.loadContent(paths, configs, readConfig.info, readConfig.main, readConfig.type);
const content = await this.loadContent(paths, configs, readConfig.info, readConfig.main);
if (reload) this.localContent[index] = content;
else this.localContent.push(content);
return content;

View File

@ -8,6 +8,25 @@
* LICENSE file in the root directory of this source tree.
*/
class ExtModuleEvents {
constructor(extmodule) {
this.extmodule = extmodule;
this.emitter = new EventEmitter();
}
on(eventname, callback) {
this.emitter.on(eventname, callback);
}
off(eventname, callback) {
this.emitter.removeListener(eventname, callback);
}
emit(...args) {
this.emitter.emit(...args);
}
}
export default class ExtModule {
constructor(pluginInternals) {
@ -31,5 +50,7 @@ export default class ExtModule {
get pluginPath() { return this.paths.contentPath }
get dirName() { return this.paths.dirName }
get enabled() { return true }
get pluginConfig() { return this.userConfig.config || [] }
get config() { return this.userConfig.config || [] }
get events() { return this.EventEmitter ? this.EventEmitter : (this.EventEmitter = new ExtModuleEvents(this)) }
}

View File

@ -38,11 +38,10 @@ export default class extends ContentManager {
static get refreshModules() { return this.refreshContent }
static get loadContent() { return this.loadModule }
static async loadModule(paths, configs, info, main, type) {
static async loadModule(paths, configs, info, main) {
return new ExtModule({ configs, info, main, paths: { contentPath: paths.contentPath, dirName: paths.dirName, mainPath: paths.mainPath } });
}
static get findModule() { return this.findContent }
static get getModuleIndex() { return this.getContentIndex }
static get getModuleByName() { return this.getContentByName }

View File

@ -10,6 +10,27 @@
import { FileUtils } from 'common';
import { Modals } from 'ui';
import { EventEmitter } from 'events';
import { SettingUpdatedEvent, SettingsUpdatedEvent } from 'structs';
class PluginEvents {
constructor(plugin) {
this.plugin = plugin;
this.emitter = new EventEmitter();
}
on(eventname, callback) {
this.emitter.on(eventname, callback);
}
off(eventname, callback) {
this.emitter.removeListener(eventname, callback);
}
emit(...args) {
this.emitter.emit(...args);
}
}
export default class Plugin {
@ -29,15 +50,17 @@ export default class Plugin {
get main() { return this.__pluginInternals.main }
get defaultConfig() { return this.configs.defaultConfig }
get userConfig() { return this.configs.userConfig }
get id() { return this.info.id || this.info.name.replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-') }
get id() { return this.info.id || this.name.replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-') }
get name() { return this.info.name }
get authors() { return this.info.authors }
get version() { return this.info.version }
get pluginPath() { return this.paths.contentPath }
get dirName() { return this.paths.dirName }
get enabled() { return this.userConfig.enabled }
get pluginConfig() { return this.userConfig.config || [] }
get config() { return this.userConfig.config || [] }
get pluginConfig() { return this.config }
get exports() { return this._exports ? this._exports : (this._exports = this.getExports()) }
get events() { return this.EventEmitter ? this.EventEmitter : (this.EventEmitter = new PluginEvents(this)) }
getSetting(setting_id, category_id) {
for (let category of this.pluginConfig) {
@ -54,21 +77,34 @@ export default class Plugin {
}
async saveSettings(newSettings) {
for (let category of newSettings) {
const oldCategory = this.pluginConfig.find(c => c.category === category.category);
for (let setting of category.settings) {
const oldSetting = oldCategory.settings.find(s => s.id === setting.id);
if (oldSetting.value === setting.value) continue;
oldSetting.value = setting.value;
if (this.settingChanged) this.settingChanged(category.category, setting.id, setting.value);
const updatedSettings = [];
for (let newCategory of newSettings) {
const category = this.pluginConfig.find(c => c.category === newCategory.category);
for (let newSetting of newCategory.settings) {
const setting = category.settings.find(s => s.id === newSetting.id);
if (setting.value === newSetting.value) continue;
let old_value = setting.value;
setting.value = newSetting.value;
updatedSettings.push({ category_id: category.category, setting_id: setting.id, value: setting.value, old_value });
this.settingUpdated(category.category, setting.id, setting.value, old_value);
}
}
this.saveConfiguration();
return this.settingsUpdated(updatedSettings);
}
if (this.settingsChanged) this.settingsChanged(this.pluginConfig);
settingUpdated(category_id, setting_id, value, old_value) {
const event = new SettingUpdatedEvent({ category_id, setting_id, value, old_value });
this.events.emit('setting-updated', event);
this.events.emit(`setting-updated_{$category_id}_${setting_id}`, event);
}
return this.pluginConfig;
settingsUpdated(updatedSettings) {
const event = new SettingsUpdatedEvent({ settings: updatedSettings.map(s => new SettingUpdatedEvent(s)) });
this.events.emit('settings-updated', event);
}
async saveConfiguration() {

View File

@ -15,7 +15,6 @@ import Vendor from './vendor';
import { ClientLogger as Logger } from 'common';
import { Events } from 'modules';
export default class extends ContentManager {
static get localPlugins() {
@ -34,8 +33,8 @@ export default class extends ContentManager {
return 'plugins';
}
static async loadAllPlugins(supressErrors) {
const loadAll = await this.loadAllContent(supressErrors);
static async loadAllPlugins(suppressErrors) {
const loadAll = await this.loadAllContent(suppressErrors);
this.localPlugins.forEach(plugin => {
if (plugin.enabled) plugin.start();
});
@ -45,7 +44,7 @@ export default class extends ContentManager {
static get refreshPlugins() { return this.refreshContent }
static get loadContent() { return this.loadPlugin }
static async loadPlugin(paths, configs, info, main, type) {
static async loadPlugin(paths, configs, info, main) {
const plugin = window.require(paths.mainPath)(Plugin, new PluginApi(info), Vendor);
const instance = new plugin({ configs, info, main, paths: { contentPath: paths.contentPath, dirName: paths.dirName, mainPath: paths.mainPath } });
return instance;

166
client/src/modules/theme.js Normal file
View File

@ -0,0 +1,166 @@
/**
* BetterDiscord Theme Module
* 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 './thememanager';
import { DOM, Modals } from 'ui';
import { FileUtils, ClientIPC } from 'common';
class ThemeEvents {
constructor(theme) {
this.theme = theme;
this.emitter = new EventEmitter();
}
on(eventname, callback) {
this.emitter.on(eventname, callback);
}
off(eventname, callback) {
this.emitter.removeListener(eventname, callback);
}
emit(...args) {
this.emitter.emit(...args);
}
}
export default class Theme {
constructor(themeInternals) {
this.__themeInternals = themeInternals;
this.hasSettings = this.themeConfig && this.themeConfig.length > 0;
this.saveSettings = this.saveSettings.bind(this);
this.enable = this.enable.bind(this);
this.disable = this.disable.bind(this);
}
get configs() { return this.__themeInternals.configs }
get info() { return this.__themeInternals.info }
get icon() { return this.info.icon }
get paths() { return this.__themeInternals.paths }
get main() { return this.__themeInternals.main }
get defaultConfig() { return this.configs.defaultConfig }
get userConfig() { return this.configs.userConfig }
get id() { return this.info.id || this.name.toLowerCase().replace(/[^a-zA-Z0-9-]/g, '-').replace(/\s+/g, '-') }
get name() { return this.info.name }
get authors() { return this.info.authors }
get version() { return this.info.version }
get themePath() { return this.paths.contentPath }
get dirName() { return this.paths.dirName }
get enabled() { return this.userConfig.enabled }
get config() { return this.userConfig.config || [] }
get themeConfig() { return this.config }
get css() { return this.userConfig.css }
get events() { return this.EventEmitter ? this.EventEmitter : (this.EventEmitter = new ThemeEvents(this)) }
showSettingsModal() {
return Modals.themeSettings(this);
}
async saveSettings(newSettings) {
const updatedSettings = [];
for (let newCategory of newSettings) {
const category = this.pluginConfig.find(c => c.category === newCategory.category);
for (let newSetting of newCategory.settings) {
const setting = category.settings.find(s => s.id === newSetting.id);
if (setting.value === newSetting.value) continue;
let old_value = setting.value;
setting.value = newSetting.value;
updatedSettings.push({ category_id: category.category, setting_id: setting.id, value: setting.value, old_value });
this.settingUpdated(category.category, setting.id, setting.value, old_value);
}
}
// As the theme's configuration has changed it needs recompiling
// When the compiled CSS has been save it will also save the configuration
await this.recompile();
return this.settingsUpdated(updatedSettings);
}
settingUpdated(category_id, setting_id, value, old_value) {
const event = new SettingUpdatedEvent({ category_id, setting_id, value, old_value });
this.events.emit('setting-updated', event);
this.events.emit(`setting-updated_{$category_id}_${setting_id}`, event);
}
settingsUpdated(updatedSettings) {
const event = new SettingsUpdatedEvent({ settings: updatedSettings.map(s => new SettingUpdatedEvent(s)) });
this.events.emit('settings-updated', event);
}
async saveConfiguration() {
try {
await FileUtils.writeFile(`${this.themePath}/user.config.json`, JSON.stringify({
enabled: this.enabled,
config: this.themeConfig.map(category => {
return {
category: category.category,
settings: category.settings.map(setting => {
return {
id: setting.id,
value: setting.value
};
})
};
}),
css: this.css
}));
} catch (err) {
throw err;
}
}
enable() {
if (!this.enabled) {
this.userConfig.enabled = true;
this.saveConfiguration();
}
DOM.injectTheme(this.css, this.id);
}
disable() {
this.userConfig.enabled = false;
this.saveConfiguration();
DOM.deleteTheme(this.id);
}
async compile() {
console.log('Compiling CSS');
let css = '';
if (this.info.type === 'sass') {
css = await ClientIPC.send('bd-compileSass', {
data: ThemeManager.getConfigAsSCSS(this.themeConfig),
path: this.paths.mainPath.replace(/\\/g, '/')
});
console.log(css);
} else {
css = await FileUtils.readFile(this.paths.mainPath);
}
return css;
}
async recompile() {
const css = await this.compile();
this.userConfig.css = css;
await this.saveConfiguration();
if (this.enabled) {
DOM.deleteTheme(this.id);
DOM.injectTheme(this.css, this.id);
}
}
}

View File

@ -9,126 +9,7 @@
*/
import ContentManager from './contentmanager';
import { DOM, Modals } from 'ui';
import { FileUtils, ClientIPC } from 'common';
class Theme {
constructor(themeInternals) {
this.__themeInternals = themeInternals;
this.hasSettings = this.themeConfig && this.themeConfig.length > 0;
this.saveSettings = this.saveSettings.bind(this);
this.enable = this.enable.bind(this);
this.disable = this.disable.bind(this);
}
get configs() { return this.__themeInternals.configs }
get info() { return this.__themeInternals.info }
get icon() { return this.info.icon }
get paths() { return this.__themeInternals.paths }
get main() { return this.__themeInternals.main }
get defaultConfig() { return this.configs.defaultConfig }
get userConfig() { return this.configs.userConfig }
get name() { return this.info.name }
get authors() { return this.info.authors }
get version() { return this.info.version }
get themePath() { return this.paths.contentPath }
get dirName() { return this.paths.dirName }
get enabled() { return this.userConfig.enabled }
get themeConfig() { return this.userConfig.config }
get css() { return this.userConfig.css }
get id() { return this.name.toLowerCase().replace(/\s+/g, '-') }
showSettingsModal() {
return Modals.themeSettings(this);
}
async saveSettings(newSettings) {
for (let category of newSettings) {
const oldCategory = this.themeConfig.find(c => c.category === category.category);
for (let setting of category.settings) {
const oldSetting = oldCategory.settings.find(s => s.id === setting.id);
if (oldSetting.value === setting.value) continue;
oldSetting.value = setting.value;
if (this.settingChanged) this.settingChanged(category.category, setting.id, setting.value);
}
}
// As the theme's configuration has changed it needs recompiling
// When the compiled CSS has been save it will also save the configuration
await this.recompile();
if (this.settingsChanged) this.settingsChanged(this.themeConfig);
return this.pluginConfig;
}
async saveConfiguration() {
try {
await FileUtils.writeFile(`${this.themePath}/user.config.json`, JSON.stringify({
enabled: this.enabled,
config: this.themeConfig.map(category => {
return {
category: category.category,
settings: category.settings.map(setting => {
return {
id: setting.id,
value: setting.value
};
})
};
}),
css: this.css
}));
} catch (err) {
throw err;
}
}
enable() {
if (!this.enabled) {
this.userConfig.enabled = true;
this.saveConfiguration();
}
DOM.injectTheme(this.css, this.id);
}
disable() {
this.userConfig.enabled = false;
this.saveConfiguration();
DOM.deleteTheme(this.id);
}
async compile() {
console.log('Compiling CSS');
let css = '';
if (this.info.type === 'sass') {
css = await ClientIPC.send('bd-compileSass', {
data: ThemeManager.getConfigAsSCSS(this.themeConfig),
path: this.paths.mainPath.replace(/\\/g, '/')
});
console.log(css);
} else {
css = await FileUtils.readFile(this.paths.mainPath);
}
return css;
}
async recompile() {
const css = await this.compile();
this.userConfig.css = css;
await this.saveConfiguration();
if (this.enabled) {
DOM.deleteTheme(this.id);
DOM.injectTheme(this.css, this.id);
}
}
}
import Theme from './theme';
export default class ThemeManager extends ContentManager {
@ -213,7 +94,6 @@ export default class ThemeManager extends ContentManager {
if (typeof value === 'string') {
return `$${name}: ${setting.scss_raw ? value : `'${setting.value.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}'`};`;
}
}
}

View File

@ -1,2 +1,3 @@
export { default as SettingUpdatedEvent } from './settingupdated';
export { default as SettingsUpdatedEvent } from './settingsupdated';
export { default as ErrorEvent } from './error';

View File

@ -0,0 +1,23 @@
/**
* BetterDiscord Settings Updated Event 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 Event from './event';
export default class SettingsUpdatedEvent extends Event {
get updatedSettings() {
return this.args.updatedSettings;
}
get __eventType() {
return 'settings-updated';
}
}

View File

@ -7,11 +7,18 @@ module.exports = (Plugin, Api, Vendor) => {
onStart() {
Events.subscribe('TEST_EVENT', this.eventTest);
Logger.log('onStart');
Logger.log(`Plugin setting "default-0" value: ${this.getSetting('default-0')}`);
Logger.log(`Internal setting "core/default/test-setting" value: ${Api.Settings.get('core', 'default', 'test-setting')}`);
Events.subscribe('setting-updated', setting => {
console.log('Received internal setting update:', setting);
Logger.log(`Plugin setting "default-0" value: ${this.getSetting('default-0')}`);
this.events.on('setting-updated', event => {
console.log('Received plugin setting update:', event);
});
this.events.on('settings-updated', event => {
console.log('Received plugin settings update:', event);
});
Logger.log(`Internal setting "core/default/test-setting" value: ${Api.Settings.get('core', 'default', 'test-setting')}`);
Events.subscribe('setting-updated', event => {
console.log('Received internal setting update:', event);
});
const exampleModule = new (Api.import('Example Module'));