diff --git a/client/src/modules/contentmanager.js b/client/src/modules/contentmanager.js index cdf89451..f3b26d39 100644 --- a/client/src/modules/contentmanager.js +++ b/client/src/modules/contentmanager.js @@ -185,6 +185,9 @@ export default class { }; const content = await this.loadContent(paths, configs, readConfig.info, readConfig.main, readConfig.dependencies); + if (!reload && this.getContentById(content.id)) + throw {message: `A ${this.contentType} with the ID ${content.id} already exists.`}; + if (reload) this.localContent[index] = content; else this.localContent.push(content); return content; @@ -194,6 +197,43 @@ export default class { } } + /** + * Unload content + * @param {any} content Content to unload + * @param {bool} reload Whether to reload the content after + */ + static async unloadContent(content, reload) { + content = this.findContent(content); + if (!content) throw {message: `Could not find a ${this.contentType} from ${content}.`}; + + try { + if (content.enabled && content.disable) content.disable(false); + if (content.enabled && content.stop) content.stop(false); + if (content.onunload) content.onunload(reload); + if (content.onUnload) content.onUnload(reload); + const index = this.getContentIndex(content); + + delete window.require.cache[window.require.resolve(content.paths.mainPath)]; + + if (reload) { + const newcontent = await this.preloadContent(content.dirName, true, index); + if (newcontent.enabled && newcontent.start) newcontent.start(false); + return newcontent; + } else this.localContent.splice(index, 1); + } catch (err) { + Logger.err(this.moduleName, err); + throw err; + } + } + + /** + * Reload content + * @param {any} content Content to reload + */ + static async reloadContent(content) { + return this.unloadContent(content, true); + } + /** * Read content config file * @param {any} configPath Config file path @@ -212,19 +252,26 @@ export default class { return FileUtils.readJsonFromFile(configPath); } + /** + * Checks if the passed object is an instance of this content type. + * @param {any} content Object to check + */ + static isThisContent(content) { + return false; + } + /** * Wildcard content finder * @param {any} wild Content name | id | path | dirname + * @param {bool} nonunique Allow searching attributes that may not be unique */ - //TODO make this nicer - static findContent(wild) { - let content = this.getContentByName(wild); - if (content) return content; - content = this.getContentById(wild); - if (content) return content; - content = this.getContentByPath(wild); - if (content) return content; - return this.getContentByDirName(wild); + static findContent(wild, nonunique) { + if (this.isThisContent(wild)) return wild; + let content; + if (content = this.getContentById(wild)) return content; + if (content = this.getContentByDirName(wild)) return content; + if (content = this.getContentByPath(wild)) return content; + if (content = nonunique && this.getContentByName(wild)) return content; } static getContentIndex(content) { return this.localContent.findIndex(c => c === content) } diff --git a/client/src/modules/extmodule.js b/client/src/modules/extmodule.js index 3ebc9ee4..c7c8937a 100644 --- a/client/src/modules/extmodule.js +++ b/client/src/modules/extmodule.js @@ -45,11 +45,12 @@ export default class ExtModule { 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.info.name.toLowerCase().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 contentPath() { return this.paths.contentPath } + get modulePath() { return this.paths.contentPath } get dirName() { return this.paths.dirName } get enabled() { return true } get config() { return this.userConfig.config || [] } diff --git a/client/src/modules/extmodulemanager.js b/client/src/modules/extmodulemanager.js index 1edbea1c..89bf2efd 100644 --- a/client/src/modules/extmodulemanager.js +++ b/client/src/modules/extmodulemanager.js @@ -39,7 +39,19 @@ export default class extends ContentManager { static get loadContent() { return this.loadModule } static async loadModule(paths, configs, info, main) { - return new ExtModule({ configs, info, main, paths: { contentPath: paths.contentPath, dirName: paths.dirName, mainPath: paths.mainPath } }); + return new ExtModule({ + configs, info, main, + paths: { + contentPath: paths.contentPath, + dirName: paths.dirName, + mainPath: paths.mainPath + } + }); + } + + static get isExtModule() { return this.isThisContent } + static isThisContent(module) { + return module instanceof ExtModule; } static get findModule() { return this.findContent } diff --git a/client/src/modules/plugin.js b/client/src/modules/plugin.js index 9ada7016..14f85de3 100644 --- a/client/src/modules/plugin.js +++ b/client/src/modules/plugin.js @@ -52,7 +52,7 @@ export default class Plugin { get defaultConfig() { return this.configs.defaultConfig } get userConfig() { return this.configs.userConfig } get configSchemes() { return this.configs.schemes } - get id() { return this.info.id || this.name.replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-') } + get id() { return this.info.id || this.name.toLowerCase().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 } @@ -123,25 +123,32 @@ export default class Plugin { } } - start() { + start(save = true) { if (this.onstart && !this.onstart()) return false; if (this.onStart && !this.onStart()) return false; if (!this.enabled) { this.userConfig.enabled = true; - this.saveConfiguration(); + if (save) this.saveConfiguration(); } return true; } - stop() { + stop(save = true) { if (this.onstop && !this.onstop()) return false; if (this.onStop && !this.onStop()) return false; - this.userConfig.enabled = false; - this.saveConfiguration(); + if (this.enabled) { + this.userConfig.enabled = false; + if (save) this.saveConfiguration(); + } + return true; } + unload() { + PluginManager.unloadPlugin(this); + } + } diff --git a/client/src/modules/pluginmanager.js b/client/src/modules/pluginmanager.js index 105e7125..a43755d6 100644 --- a/client/src/modules/pluginmanager.js +++ b/client/src/modules/pluginmanager.js @@ -35,10 +35,12 @@ export default class extends ContentManager { } static async loadAllPlugins(suppressErrors) { + this.loaded = false; const loadAll = await this.loadAllContent(suppressErrors); - this.localPlugins.forEach(plugin => { + this.loaded = true; + for (let plugin of this.localPlugins) { if (plugin.enabled) plugin.start(); - }); + } return loadAll; } @@ -46,7 +48,6 @@ export default class extends ContentManager { static get loadContent() { return this.loadPlugin } static async loadPlugin(paths, configs, info, main, dependencies) { - const deps = []; if (dependencies) { for (const [key, value] of Object.entries(dependencies)) { @@ -61,36 +62,21 @@ export default class extends ContentManager { } 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 } }); + const instance = new plugin({ + configs, info, main, + paths: { + contentPath: paths.contentPath, + dirName: paths.dirName, + mainPath: paths.mainPath + } + }); + + if (instance.enabled && this.loaded) instance.start(); return instance; } - static get unloadContent() { return this.unloadPlugin } - static async unloadPlugin(plugin) { - try { - if (plugin.enabled) plugin.stop(); - const { pluginPath } = plugin; - const index = this.getPluginIndex(plugin); - - delete window.require.cache[window.require.resolve(pluginPath)]; - this.localPlugins.splice(index, 1); - } catch (err) { - //This might fail but we don't have any other option at this point - Logger.err('PluginManager', err); - } - } - - static async reloadPlugin(plugin) { - const _plugin = plugin instanceof Plugin ? plugin : this.findPlugin(plugin); - if (!_plugin) throw { 'message': 'Attempted to reload a plugin that is not loaded?' }; - if (!_plugin.stop()) throw { 'message': 'Plugin failed to stop!' }; - const index = this.getPluginIndex(_plugin); - const { pluginPath, dirName } = _plugin; - - delete window.require.cache[window.require.resolve(pluginPath)]; - - return this.preloadContent(dirName, true, index); - } + static get unloadPlugin() { return this.unloadContent } + static get reloadPlugin() { return this.reloadContent } static stopPlugin(name) { const plugin = name instanceof Plugin ? name : this.getPluginByName(name); @@ -112,6 +98,11 @@ export default class extends ContentManager { return true; //Return true anyways since plugin doesn't exist } + static get isPlugin() { return this.isThisContent } + static isThisContent(plugin) { + return plugin instanceof Plugin; + } + static get findPlugin() { return this.findContent } static get getPluginIndex() { return this.getContentIndex } static get getPluginByName() { return this.getContentByName } diff --git a/client/src/modules/theme.js b/client/src/modules/theme.js index 568a1093..3755b63f 100644 --- a/client/src/modules/theme.js +++ b/client/src/modules/theme.js @@ -49,6 +49,7 @@ export default class Theme { get icon() { return this.info.icon } get paths() { return this.__themeInternals.paths } get main() { return this.__themeInternals.main } + get loaded() { return this.__themeInternals.loaded } get defaultConfig() { return this.configs.defaultConfig } get userConfig() { return this.configs.userConfig } get configSchemes() { return this.configs.schemes } @@ -56,6 +57,7 @@ export default class Theme { get name() { return this.info.name } get authors() { return this.info.authors } get version() { return this.info.version } + get contentPath() { return this.paths.contentPath } get themePath() { return this.paths.contentPath } get dirName() { return this.paths.dirName } get enabled() { return this.userConfig.enabled } @@ -115,17 +117,17 @@ export default class Theme { } } - enable() { + enable(save = true) { if (!this.enabled) { this.userConfig.enabled = true; - this.saveConfiguration(); + if (save) this.saveConfiguration(); } DOM.injectTheme(this.css, this.id); } - disable() { + disable(save = true) { this.userConfig.enabled = false; - this.saveConfiguration(); + if (save) this.saveConfiguration(); DOM.deleteTheme(this.id); } diff --git a/client/src/modules/thememanager.js b/client/src/modules/thememanager.js index 18490350..ada6a968 100644 --- a/client/src/modules/thememanager.js +++ b/client/src/modules/thememanager.js @@ -30,9 +30,8 @@ export default class ThemeManager extends ContentManager { return 'themes'; } - static get loadAllThemes() { - return this.loadAllContent; - } + static get loadAllThemes() { return this.loadAllContent } + static get refreshThemes() { return this.refreshContent } static get loadContent() { return this.loadTheme } static async loadTheme(paths, configs, info, main) { @@ -53,6 +52,9 @@ export default class ThemeManager extends ContentManager { } } + static get unloadTheme() { return this.unloadContent } + static get reloadTheme() { return this.reloadContent } + static enableTheme(theme) { theme.enable(); } @@ -61,10 +63,17 @@ export default class ThemeManager extends ContentManager { theme.disable(); } - static reloadTheme(theme) { + static get unloadTheme() { return this.unloadContent } + static async reloadTheme(theme) { + theme = await this.reloadContent(theme); theme.recompile(); } + static get isTheme() { return this.isThisContent } + static isThisContent(theme) { + return theme instanceof Theme; + } + static async getConfigAsSCSS(config) { const variables = []; diff --git a/client/src/ui/components/bd/PluginCard.vue b/client/src/ui/components/bd/PluginCard.vue index 08851ae9..901f3b30 100644 --- a/client/src/ui/components/bd/PluginCard.vue +++ b/client/src/ui/components/bd/PluginCard.vue @@ -15,7 +15,7 @@ - + @@ -31,7 +31,7 @@ settingsOpen: false } }, - props: ['plugin', 'togglePlugin', 'reloadPlugin', 'showSettings'], + props: ['plugin', 'togglePlugin', 'reloadPlugin', 'deletePlugin', 'showSettings'], components: { Card, Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension }, diff --git a/client/src/ui/components/bd/PluginsView.vue b/client/src/ui/components/bd/PluginsView.vue index 8beb7b96..d2c2b8cf 100644 --- a/client/src/ui/components/bd/PluginsView.vue +++ b/client/src/ui/components/bd/PluginsView.vue @@ -23,7 +23,7 @@
- +
@@ -67,11 +67,8 @@ async togglePlugin(plugin) { // TODO Display error if plugin fails to start/stop try { - if (plugin.enabled) { - await PluginManager.stopPlugin(plugin); - } else { - await PluginManager.startPlugin(plugin); - } + await plugin.enabled ? PluginManager.stopPlugin(plugin) : PluginManager.startPlugin(plugin); + this.$forceUpdate(); } catch (err) { console.log(err); } @@ -84,6 +81,15 @@ console.log(err); } }, + async deletePlugin(plugin, unload) { + try { + if (unload) await PluginManager.unloadPlugin(plugin); + else await PluginManager.deletePlugin(plugin); + this.$forceUpdate(); + } catch (err) { + console.error(err); + } + }, showSettings(plugin) { return Modals.contentSettings(plugin); } diff --git a/client/src/ui/components/bd/ThemeCard.vue b/client/src/ui/components/bd/ThemeCard.vue index 95ee76b7..5776f4a0 100644 --- a/client/src/ui/components/bd/ThemeCard.vue +++ b/client/src/ui/components/bd/ThemeCard.vue @@ -13,9 +13,9 @@ - + - + @@ -31,7 +31,7 @@ settingsOpen: false } }, - props: ['theme', 'toggleTheme', 'reloadTheme', 'showSettings'], + props: ['theme', 'toggleTheme', 'reloadTheme', 'deleteTheme', 'showSettings'], components: { Card, Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension }, diff --git a/client/src/ui/components/bd/ThemesView.vue b/client/src/ui/components/bd/ThemesView.vue index 427f0b7b..db8c3f65 100644 --- a/client/src/ui/components/bd/ThemesView.vue +++ b/client/src/ui/components/bd/ThemesView.vue @@ -23,7 +23,7 @@
- +
@@ -59,31 +59,38 @@ this.local = false; }, async refreshLocal() { - await ThemeManager.refreshTheme(); + await ThemeManager.refreshThemes(); }, async refreshOnline() { }, - toggleTheme(theme) { + async toggleTheme(theme) { // TODO Display error if theme fails to enable/disable try { - if (theme.enabled) { - ThemeManager.disableTheme(theme); - } else { - ThemeManager.enableTheme(theme); - } + await theme.enabled ? ThemeManager.disableTheme(theme) : ThemeManager.enableTheme(theme); + this.$forceUpdate(); } catch (err) { console.log(err); } }, - async reloadTheme(theme) { + async reloadTheme(theme, reload) { try { - await ThemeManager.reloadTheme(theme); + if (reload) await ThemeManager.reloadTheme(theme); + else await theme.recompile(); this.$forceUpdate(); } catch (err) { console.log(err); } }, + async deleteTheme(theme, unload) { + try { + if (unload) await ThemeManager.unloadTheme(theme); + else await ThemeManager.deleteTheme(theme); + this.$forceUpdate(); + } catch (err) { + console.error(err); + } + }, showSettings(theme) { return Modals.contentSettings(theme); } diff --git a/client/src/ui/components/common/Button.vue b/client/src/ui/components/common/Button.vue index a41b758e..917f74e3 100644 --- a/client/src/ui/components/common/Button.vue +++ b/client/src/ui/components/common/Button.vue @@ -9,7 +9,7 @@ */