diff --git a/client/src/modules/contentmanager.js b/client/src/modules/contentmanager.js index d7069696..aaebcb10 100644 --- a/client/src/modules/contentmanager.js +++ b/client/src/modules/contentmanager.js @@ -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; diff --git a/client/src/modules/extmodule.js b/client/src/modules/extmodule.js index 7973037f..47081ac3 100644 --- a/client/src/modules/extmodule.js +++ b/client/src/modules/extmodule.js @@ -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)) } + } diff --git a/client/src/modules/extmodulemanager.js b/client/src/modules/extmodulemanager.js index a9e0f8fe..1edbea1c 100644 --- a/client/src/modules/extmodulemanager.js +++ b/client/src/modules/extmodulemanager.js @@ -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 } diff --git a/client/src/modules/plugin.js b/client/src/modules/plugin.js index 9ce047fd..c4ae5c9d 100644 --- a/client/src/modules/plugin.js +++ b/client/src/modules/plugin.js @@ -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() { diff --git a/client/src/modules/pluginmanager.js b/client/src/modules/pluginmanager.js index 4b3416a5..6c99634f 100644 --- a/client/src/modules/pluginmanager.js +++ b/client/src/modules/pluginmanager.js @@ -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; diff --git a/client/src/modules/theme.js b/client/src/modules/theme.js new file mode 100644 index 00000000..a80c3c7e --- /dev/null +++ b/client/src/modules/theme.js @@ -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); + } + } + +} diff --git a/client/src/modules/thememanager.js b/client/src/modules/thememanager.js index 1b9bf2c6..669b23c3 100644 --- a/client/src/modules/thememanager.js +++ b/client/src/modules/thememanager.js @@ -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, '\\\'')}'`};`; } - } } diff --git a/client/src/structs/events/index.js b/client/src/structs/events/index.js index 354e4192..f10d1e35 100644 --- a/client/src/structs/events/index.js +++ b/client/src/structs/events/index.js @@ -1,2 +1,3 @@ export { default as SettingUpdatedEvent } from './settingupdated'; +export { default as SettingsUpdatedEvent } from './settingsupdated'; export { default as ErrorEvent } from './error'; diff --git a/client/src/structs/events/settingsupdated.js b/client/src/structs/events/settingsupdated.js new file mode 100644 index 00000000..99470ea7 --- /dev/null +++ b/client/src/structs/events/settingsupdated.js @@ -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'; + } + +} diff --git a/tests/plugins/Example/index.js b/tests/plugins/Example/index.js index ee5fb848..48beaddf 100644 --- a/tests/plugins/Example/index.js +++ b/tests/plugins/Example/index.js @@ -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'));