From 9301ad627cb0769e6ef17c8da8950f9b7d04a7cf Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Sun, 11 Feb 2018 19:31:24 +0000 Subject: [PATCH] Add SCSS theme configuration and compile caching --- client/src/modules/contentmanager.js | 3 +- client/src/modules/thememanager.js | 119 +++++++++++++++++++++------ tests/themes/Example/config.json | 10 ++- 3 files changed, 103 insertions(+), 29 deletions(-) diff --git a/client/src/modules/contentmanager.js b/client/src/modules/contentmanager.js index d409e76e..18a91f3b 100644 --- a/client/src/modules/contentmanager.js +++ b/client/src/modules/contentmanager.js @@ -42,7 +42,7 @@ export default class { message: `Failed to load ${dir}`, err })); - + Logger.err(this.moduleName, err); } } @@ -134,6 +134,7 @@ export default class { }); return config; }); + userConfig.css = readUserConfig.css || null; // userConfig.config = readUserConfig.config; } catch (err) { /*We don't care if this fails it either means that user config doesn't exist or there's something wrong with it so we revert to default config*/ diff --git a/client/src/modules/thememanager.js b/client/src/modules/thememanager.js index cbf11716..f5fb3180 100644 --- a/client/src/modules/thememanager.js +++ b/client/src/modules/thememanager.js @@ -36,48 +36,87 @@ class Theme { get dirName() { return this.paths.dirName } get enabled() { return this.userConfig.enabled } get themeConfig() { return this.userConfig.config } - get css() { return this.__themeInternals.css } + get css() { return this.userConfig.css } get id() { return this.name.toLowerCase().replace(/\s+/g, '-') } async saveSettings(newSettings) { - if (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); - } + 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); } } - try { - await FileUtils.writeFile(`${this.themePath}/user.config.json`, JSON.stringify({ enabled: this.enabled, config: this.themeConfig })); - } catch (err) { - throw err; - } + // 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, + css: this.css + })); + } catch (err) { + throw err; + } + } + enable() { - this.userConfig.enabled = true; - this.saveSettings(); + if (!this.enabled) { + this.userConfig.enabled = true; + this.saveConfiguration(); + } DOM.injectTheme(this.css, this.id); } disable() { this.userConfig.enabled = false; - this.saveSettings(); + 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', { + scss: ThemeManager.getConfigAsSCSS(this.themeConfig), + path: this.paths.mainPath + }); + 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); + } + } + } -export default class extends ContentManager { +export default class ThemeManager extends ContentManager { static get localThemes() { return this.localContent; @@ -94,14 +133,16 @@ export default class extends ContentManager { static get loadContent() { return this.loadTheme } static async loadTheme(paths, configs, info, main) { try { - let css = ''; - if (info.type === 'sass') { - css = await ClientIPC.send('bd-compileSass', { path: paths.mainPath }); - } else { - css = await FileUtils.readFile(paths.mainPath); - } - const instance = new Theme({ configs, info, main, paths: { contentPath: paths.contentPath, dirName: paths.dirName }, css }); - if (instance.enabled) instance.enable(); + const instance = new Theme({ + configs, info, main, + paths: { + contentPath: paths.contentPath, + dirName: paths.dirName, + mainPath: paths.mainPath + } + }); + if (!instance.css) instance.recompile(); + else if (instance.enabled) instance.enable(); return instance; } catch (err) { throw err; @@ -116,4 +157,28 @@ export default class extends ContentManager { theme.disable(); } + static getConfigAsSCSS(config) { + const variables = []; + + for (let category of config) { + for (let setting of category.settings) { + let scss_name = null; + let scss_value = null; + let scss_line = null; + + if (typeof setting.value == 'string') + scss_value = setting.scss_raw ? setting.value : '\'' + setting.value.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + '\''; + else if (typeof setting.value === 'boolean' || typeof setting.value === 'number') + scss_value = setting.value.toString(); + + scss_name = setting.id.replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-'); + + scss_line = `$${scss_name}: ${scss_value};`; + variables.push(scss_line); + } + } + + return variables.join('\n'); + } + } diff --git a/tests/themes/Example/config.json b/tests/themes/Example/config.json index 6a5ee81f..fa3813d2 100644 --- a/tests/themes/Example/config.json +++ b/tests/themes/Example/config.json @@ -5,13 +5,21 @@ "version": 1.0, "description": "Example Theme 1 Description", "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FscXVlXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg==", - "type": "sass" + "type": "sass" }, "main": "index.scss", "defaultConfig": [ { "category": "default", "settings": [ + { + "id": "divBg", + "type": "text", + "value": "#00ff00", + "text": "Primary colour", + "hint": "A colour setting type would be nice here", + "scss_raw": true + }, { "id": "default-0", "type": "text",