diff --git a/client/src/modules/contentconfig.js b/client/src/modules/contentconfig.js
new file mode 100644
index 00000000..b5c23cca
--- /dev/null
+++ b/client/src/modules/contentconfig.js
@@ -0,0 +1,31 @@
+/**
+ * BetterDiscord Content Config Utility
+ * 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.
+*/
+
+
+export default class ContentConfig {
+
+ constructor(data) {
+ this.data = data;
+ }
+
+ map(cb) {
+ return this.data.map(cb);
+ }
+
+ strip() {
+ return this.map(cat => ({
+ category: cat.category,
+ settings: cat.settings.map(setting => ({
+ id: setting.id, value: setting.value
+ }))
+ }));
+ }
+
+}
diff --git a/client/src/modules/contentmanager.js b/client/src/modules/contentmanager.js
index a4835847..c53bbaf1 100644
--- a/client/src/modules/contentmanager.js
+++ b/client/src/modules/contentmanager.js
@@ -12,24 +12,43 @@ import Globals from './globals';
import { FileUtils, ClientLogger as Logger } from 'common';
import path from 'path';
import { Events } from 'modules';
-import { Error } from 'structs';
+import { ErrorEvent } from 'structs';
import { Modals } from 'ui';
+/**
+ * Base class for external content managing
+ */
export default class {
+ /**
+ * Any errors that happened
+ * returns {Array}
+ */
static get errors() {
return this._errors || (this._errors = []);
}
+ /**
+ * Locallly stored content
+ * returns {Array}
+ */
static get localContent() {
return this._localContent ? this._localContent : (this._localContent = []);
}
+ /**
+ * Local path for content
+ * returns {String}
+ */
static get contentPath() {
return this._contentPath ? this._contentPath : (this._contentPath = Globals.getObject('paths').find(path => path.id === this.pathId).path);
}
- static async loadAllContent(supressErrors) {
+ /**
+ * Load all locally stored content
+ * @param {bool} suppressErrors Suppress any errors that occur during loading of content
+ */
+ static async loadAllContent(suppressErrors = false) {
try {
await FileUtils.ensureDirectory(this.contentPath);
const directories = await FileUtils.listDirectory(this.contentPath);
@@ -38,7 +57,7 @@ export default class {
try {
await this.preloadContent(dir);
} catch (err) {
- this.errors.push(new Error({
+ this.errors.push(new ErrorEvent({
module: this.moduleName,
message: `Failed to load ${dir}`,
err
@@ -48,7 +67,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,
@@ -64,6 +83,9 @@ export default class {
}
}
+ /**
+ * Refresh locally stored content
+ */
static async refreshContent() {
if (!this.localContent.length) return this.loadAllContent();
@@ -97,6 +119,12 @@ export default class {
}
}
+ /**
+ * Common loading procedure for loading content before passing it to the actual loader
+ * @param {any} dirName Base directory for content
+ * @param {any} reload Is content being reloaded
+ * @param {any} index Index of content in {localContent}
+ */
static async preloadContent(dirName, reload = false, index) {
try {
const contentPath = path.join(this.contentPath, dirName);
@@ -154,7 +182,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, readConfig.dependencies);
if (reload) this.localContent[index] = content;
else this.localContent.push(content);
return content;
@@ -164,16 +192,28 @@ export default class {
}
}
+ /**
+ * Read content config file
+ * @param {any} configPath Config file path
+ */
static async readConfig(configPath) {
configPath = path.resolve(configPath, 'config.json');
return FileUtils.readJsonFromFile(configPath);
}
+ /**
+ * Read content user config file
+ * @param {any} configPath User config file path
+ */
static async readUserConfig(configPath) {
configPath = path.resolve(configPath, 'user.config.json');
return FileUtils.readJsonFromFile(configPath);
}
+ /**
+ * Wildcard content finder
+ * @param {any} wild Content name | id | path | dirname
+ */
//TODO make this nicer
static findContent(wild) {
let content = this.getContentByName(wild);
@@ -191,6 +231,10 @@ export default class {
static getContentByPath(path) { return this.localContent.find(c => c.contentPath === path) }
static getContentByDirName(dirName) { return this.localContent.find(c => c.dirName === dirName) }
+ /**
+ * Wait for content to load
+ * @param {any} content_id
+ */
static waitForContent(content_id) {
return new Promise((resolve, reject) => {
const check = () => {
diff --git a/client/src/modules/csseditor.js b/client/src/modules/csseditor.js
index c6614417..9f686ded 100644
--- a/client/src/modules/csseditor.js
+++ b/client/src/modules/csseditor.js
@@ -12,8 +12,14 @@ import { ClientIPC } from 'common';
import Settings from './settings';
import { DOM } from 'ui';
+/**
+ * Custom css editor communications
+ */
export default class {
+ /**
+ * Init css editor
+ */
static init() {
ClientIPC.on('bd-get-scss', () => this.sendToEditor('set-scss', { scss: this.scss }));
ClientIPC.on('bd-update-scss', (e, scss) => this.updateScss(scss));
@@ -25,10 +31,18 @@ export default class {
});
}
+ /**
+ * Show css editor, flashes if already visible
+ */
static async show() {
await ClientIPC.send('openCssEditor', this.editor_bounds);
}
+ /**
+ * Update css in client
+ * @param {String} scss scss to compile
+ * @param {bool} sendSource send to css editor instance
+ */
static updateScss(scss, sendSource) {
if (sendSource)
this.sendToEditor('set-scss', { scss });
@@ -46,31 +60,57 @@ export default class {
});
}
+ /**
+ * Save css to file
+ */
static async save() {
Settings.saveSettings();
}
+ /**
+ * Save current editor bounds
+ * @param {Rectangle} bounds editor bounds
+ */
static saveEditorBounds(bounds) {
this.editor_bounds = bounds;
Settings.saveSettings();
}
+ /**
+ * Send scss to core for compilation
+ * @param {String} scss scss string
+ */
static async compile(scss) {
return await ClientIPC.send('bd-compileSass', { data: scss });
}
+ /**
+ * Send css to open editor
+ * @param {any} channel
+ * @param {any} data
+ */
static async sendToEditor(channel, data) {
return await ClientIPC.send('sendToCssEditor', { channel, data });
}
+ /**
+ * Current uncompiled scss
+ */
static get scss() {
return this._scss || '';
}
+ /**
+ * Set current scss
+ */
static set scss(scss) {
this.updateScss(scss, true);
}
+ /**
+ * Inject compiled css to head
+ * {DOM}
+ */
static set css(css) {
DOM.injectStyle(css, 'bd-customcss');
}
diff --git a/client/src/modules/eventhook.js b/client/src/modules/eventhook.js
index 7b5f8ca8..399fa6ac 100644
--- a/client/src/modules/eventhook.js
+++ b/client/src/modules/eventhook.js
@@ -17,6 +17,10 @@ import {
} from '../structs/socketstructs';
+/**
+ * Discord socket event hook
+ * @extends {EventListener}
+ */
export default class extends EventListener {
bindings() {
@@ -29,14 +33,16 @@ export default class extends EventListener {
];
}
- hook() {
+ hook() {}
- }
-
- get eventsModule() {
-
- }
+ get eventsModule() {}
+ /**
+ * Discord emit overload
+ * @param {any} e
+ * @param {any} action
+ * @param {any} data
+ */
emit(e, action, data) {
switch (e) {
case 'dispatch':
@@ -44,6 +50,11 @@ export default class extends EventListener {
}
}
+ /**
+ * Emit callback
+ * @param {any} e Event Action
+ * @param {any} d Event Args
+ */
dispatch(e, d) {
Events.emit('raw-event', { type: e, data: d });
@@ -77,6 +88,9 @@ export default class extends EventListener {
}
}
+ /**
+ * All known socket actions
+ */
get actions() {
return {
READY: 'READY', // Socket ready
diff --git a/client/src/modules/extmodule.js b/client/src/modules/extmodule.js
index 7973037f..3ebc9ee4 100644
--- a/client/src/modules/extmodule.js
+++ b/client/src/modules/extmodule.js
@@ -8,6 +8,27 @@
* LICENSE file in the root directory of this source tree.
*/
+import { EventEmitter } from 'events';
+
+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 +52,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 b104c921..68955d5e 100644
--- a/client/src/modules/plugin.js
+++ b/client/src/modules/plugin.js
@@ -10,6 +10,28 @@
import { Utils, FileUtils } from 'common';
import { Modals } from 'ui';
+import { EventEmitter } from 'events';
+import ContentConfig from './contentconfig';
+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,7 +51,7 @@ 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 }
@@ -39,6 +61,7 @@ export default class Plugin {
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) {
@@ -55,38 +78,43 @@ 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 (Utils.compare(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 (Utils.compare(setting.value, newSetting.value)) continue;
+
+ const 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() {
+ window.testConfig = new ContentConfig(this.pluginConfig);
try {
+ const config = new ContentConfig(this.pluginConfig).strip();
await FileUtils.writeFile(`${this.pluginPath}/user.config.json`, JSON.stringify({
enabled: this.enabled,
- config: this.config.map(category => {
- return {
- category: category.category,
- settings: category.settings.map(setting => {
- return {
- id: setting.id,
- value: setting.value
- };
- })
- };
- })
+ config
}));
} catch (err) {
throw err;
diff --git a/client/src/modules/pluginapi.js b/client/src/modules/pluginapi.js
index 0906ee01..3ad97630 100644
--- a/client/src/modules/pluginapi.js
+++ b/client/src/modules/pluginapi.js
@@ -9,6 +9,7 @@
*/
import { ClientLogger as Logger } from 'common';
+import Settings from './settings';
import ExtModuleManager from './extmodulemanager';
import PluginManager from './pluginmanager';
import ThemeManager from './thememanager';
@@ -67,6 +68,15 @@ export default class PluginApi {
}
}
+ getSetting(set, category, setting) {
+ return Settings.get(set, category, setting);
+ }
+ get Settings() {
+ return {
+ get: this.getSetting.bind(this)
+ };
+ }
+
async getPlugin(plugin_id) {
// This should require extra permissions
return await PluginManager.waitForPlugin(plugin_id);
diff --git a/client/src/modules/pluginmanager.js b/client/src/modules/pluginmanager.js
index 4b3416a5..105e7125 100644
--- a/client/src/modules/pluginmanager.js
+++ b/client/src/modules/pluginmanager.js
@@ -9,13 +9,13 @@
*/
import ContentManager from './contentmanager';
+import ExtModuleManager from './extmodulemanager';
import Plugin from './plugin';
import PluginApi from './pluginapi';
import Vendor from './vendor';
import { ClientLogger as Logger } from 'common';
import { Events } from 'modules';
-
export default class extends ContentManager {
static get localPlugins() {
@@ -34,8 +34,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,8 +45,22 @@ 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) {
- const plugin = window.require(paths.mainPath)(Plugin, new PluginApi(info), Vendor);
+ static async loadPlugin(paths, configs, info, main, dependencies) {
+
+ const deps = [];
+ if (dependencies) {
+ for (const [key, value] of Object.entries(dependencies)) {
+ const extModule = ExtModuleManager.findModule(key);
+ if (!extModule) {
+ throw {
+ 'message': `Dependency: ${key}:${value} is not loaded`
+ };
+ }
+ deps[key] = extModule.__require;
+ }
+ }
+
+ const plugin = window.require(paths.mainPath)(Plugin, new PluginApi(info), Vendor, deps);
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/settings.js b/client/src/modules/settings.js
index 872ce3e4..16c6e64e 100644
--- a/client/src/modules/settings.js
+++ b/client/src/modules/settings.js
@@ -11,7 +11,9 @@
import defaultSettings from '../data/user.settings.default';
import Globals from './globals';
import CssEditor from './csseditor';
+import Events from './events';
import { FileUtils, ClientLogger as Logger } from 'common';
+import { SettingUpdatedEvent } from 'structs';
import path from 'path';
export default class {
@@ -43,7 +45,7 @@ export default class {
}
CssEditor.updateScss(scss, true);
- CssEditor.editor_bounds = css_editor_bounds;
+ CssEditor.editor_bounds = css_editor_bounds || {};
} catch (err) {
// There was an error loading settings
// This probably means that the user doesn't have any settings yet
@@ -122,8 +124,9 @@ export default class {
if (setting.id !== setting_id) continue;
if (setting.value === value) return true;
+ let old_value = setting.value;
setting.value = value;
- this.settingUpdated(set_id, category_id, setting_id, value);
+ this.settingUpdated(set_id, category_id, setting_id, value, old_value);
return true;
}
}
@@ -132,8 +135,12 @@ export default class {
return false;
}
- static settingUpdated(set_id, category_id, setting_id, value) {
- Logger.log('Settings', `${set_id}/${category_id}/${setting_id} was set to ${value}`);
+ static settingUpdated(set_id, category_id, setting_id, value, old_value) {
+ Logger.log('Settings', `${set_id}/${category_id}/${setting_id} was changed from ${old_value} to ${value}`);
+
+ const event = new SettingUpdatedEvent({ set_id, category_id, setting_id, value, old_value });
+ Events.emit('setting-updated', event);
+ Events.emit(`setting-updated-${set_id}_{$category_id}_${setting_id}`, event);
}
static get getSettings() {
diff --git a/client/src/modules/theme.js b/client/src/modules/theme.js
new file mode 100644
index 00000000..5cf124e9
--- /dev/null
+++ b/client/src/modules/theme.js
@@ -0,0 +1,160 @@
+/**
+ * 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 { EventEmitter } from 'events';
+import { SettingUpdatedEvent, SettingsUpdatedEvent } from 'structs';
+import { DOM, Modals } from 'ui';
+import { FileUtils, ClientIPC } from 'common';
+import ContentConfig from './contentconfig';
+
+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.themeConfig.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;
+
+ const 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 {
+ const config = new ContentConfig(this.themeConfig).strip();
+ await FileUtils.writeFile(`${this.themePath}/user.config.json`, JSON.stringify({
+ enabled: this.enabled,
+ config,
+ 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/error.js b/client/src/structs/events/error.js
similarity index 81%
rename from client/src/structs/error.js
rename to client/src/structs/events/error.js
index df6a8c16..04f8bf8f 100644
--- a/client/src/structs/error.js
+++ b/client/src/structs/events/error.js
@@ -8,10 +8,12 @@
* LICENSE file in the root directory of this source tree.
*/
-export class Error {
+import Event from './event';
+
+export default class ErrorEvent extends Event {
constructor(args) {
- this.args = args;
+ super(args);
this.showStack = false; // For error modal
}
@@ -31,8 +33,8 @@ export class Error {
return this.err.stack;
}
- get _type() {
- return 'err';
+ get __eventType() {
+ return 'error';
}
}
diff --git a/client/src/structs/events/event.js b/client/src/structs/events/event.js
new file mode 100644
index 00000000..1159910e
--- /dev/null
+++ b/client/src/structs/events/event.js
@@ -0,0 +1,30 @@
+/**
+ * BetterDiscord Base 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.
+*/
+
+export default class Event {
+
+ constructor(args) {
+ this.__eventInfo = {
+ args: arguments,
+ type: this.__eventType
+ };
+ }
+
+ get event() {
+ return this.__eventInfo;
+ }
+
+ get args() {
+ return this.event.args[0];
+ }
+
+ get __eventType() { return null; }
+
+}
diff --git a/client/src/structs/events/index.js b/client/src/structs/events/index.js
new file mode 100644
index 00000000..f10d1e35
--- /dev/null
+++ b/client/src/structs/events/index.js
@@ -0,0 +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/client/src/structs/events/settingupdated.js b/client/src/structs/events/settingupdated.js
new file mode 100644
index 00000000..0cc8ea08
--- /dev/null
+++ b/client/src/structs/events/settingupdated.js
@@ -0,0 +1,39 @@
+/**
+ * BetterDiscord Setting 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 SettingUpdatedEvent extends Event {
+
+ get set() {
+ return this.args.set_id;
+ }
+
+ get category() {
+ return this.args.category_id;
+ }
+
+ get setting() {
+ return this.args.setting_id;
+ }
+
+ get value() {
+ return this.args.value;
+ }
+
+ get old_value() {
+ return this.args.old_value;
+ }
+
+ get __eventType() {
+ return 'setting-updated';
+ }
+
+}
diff --git a/client/src/structs/structs.js b/client/src/structs/structs.js
index 93ae819e..4042b56e 100644
--- a/client/src/structs/structs.js
+++ b/client/src/structs/structs.js
@@ -1 +1 @@
-export * from './error';
+export * from './events/index';
diff --git a/client/src/ui/components/bd/modals/ErrorModal.vue b/client/src/ui/components/bd/modals/ErrorModal.vue
index 8764248b..dec525ea 100644
--- a/client/src/ui/components/bd/modals/ErrorModal.vue
+++ b/client/src/ui/components/bd/modals/ErrorModal.vue
@@ -4,7 +4,7 @@