Add automatic recompile of SCSS in themes and the CSS editor when any file is changed

This commit is contained in:
Samuel Elliott 2018-03-03 01:43:54 +00:00
parent a6db490f49
commit 9bc29cc66e
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
11 changed files with 136 additions and 19 deletions

View File

@ -20,6 +20,7 @@ class BetterDiscord {
window.ClientIPC = ClientIPC;
window.css = CssEditor;
window.pm = PluginManager;
window.tm = ThemeManager;
window.events = Events;
window.wpm = WebpackModules;
window.bdsettings = Settings;

View File

@ -195,7 +195,7 @@ export default class {
userConfig.enabled = readUserConfig.enabled || false;
userConfig.config.merge({ settings: readUserConfig.config });
userConfig.config.setSaved();
userConfig.css = readUserConfig.css || null;
userConfig.data = readUserConfig.data || undefined;
} 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*/
console.info(`Failed reading config for ${this.contentType} ${readConfig.info.name} in ${dirName}`);
console.error(err);

View File

@ -11,6 +11,7 @@
import { ClientIPC } from 'common';
import Settings from './settings';
import { DOM } from 'ui';
import filewatcher from 'filewatcher';
/**
* Custom css editor communications
@ -29,6 +30,12 @@ export default class {
await this.updateScss(scss);
await this.save();
});
this.filewatcher = filewatcher();
this.filewatcher.on('change', (file, stat) => {
// Recompile SCSS
this.updateScss(this.scss);
});
}
/**
@ -54,9 +61,10 @@ export default class {
}
return new Promise((resolve, reject) => {
this.compile(scss).then(css => {
this.css = css;
this.compile(scss).then(result => {
this.css = result.css.toString();
this._scss = scss;
this.files = result.stats.includedFiles;
this.sendToEditor('scss-error', null);
resolve();
}).catch(err => {
@ -87,7 +95,9 @@ export default class {
* @param {String} scss scss string
*/
static async compile(scss) {
return await ClientIPC.send('bd-compileSass', { data: scss });
const result = await ClientIPC.send('bd-compileSass', { data: scss });
console.log('Custom CSS SCSS compiler result:', result, '- CSS:', result.css.toString());
return result;
}
/**
@ -121,4 +131,33 @@ export default class {
DOM.injectStyle(css, 'bd-customcss');
}
/**
* An array of files that are being watched for changes.
* @returns {Array} Files being watched
*/
static get files() {
return this._files || (this._files = []);
}
/**
* Sets all files to be watched.
* @param {Array} files Files to watch
*/
static set files(files) {
for (let file of files) {
if (!this.files.includes(file)) {
this.filewatcher.add(file);
this.files.push(file);
}
}
for (let index in this.files) {
const file = this.files[index];
if (!files.includes(file)) {
this.filewatcher.remove(file);
this.files.splice(index, 1);
}
}
}
}

View File

@ -68,6 +68,7 @@ export default class Plugin {
get settings() { return this.userConfig.config }
get config() { return this.settings.settings }
get pluginConfig() { return this.config }
get data() { return this.userConfig.data }
get exports() { return this._exports ? this._exports : (this._exports = this.getExports()) }
get events() { return this.EventEmitter ? this.EventEmitter : (this.EventEmitter = new PluginEvents(this)) }
@ -96,7 +97,8 @@ export default class Plugin {
try {
await FileUtils.writeFile(`${this.pluginPath}/user.config.json`, JSON.stringify({
enabled: this.enabled,
config: this.settings.strip().settings
config: this.settings.strip().settings,
data: this.data
}));
this.settings.setSaved();

View File

@ -115,7 +115,7 @@ export default class PluginApi {
async injectSass(id, scss, options) {
// In most cases a plugin's styles should be precompiled instead of using this
if (id && !scss && !options) scss = id, id = undefined;
const css = await this.compileSass(scss, options);
const css = (await this.compileSass(scss, options)).css.toString();
this.injectStyle(id, css, options);
}
deleteStyle(id) {

View File

@ -27,7 +27,7 @@ export default new class Settings {
const settingsPath = path.resolve(this.dataPath, 'user.settings.json');
const user_config = await FileUtils.readJsonFromFile(settingsPath);
const { settings, scss, css_editor_bounds } = user_config;
const { settings, scss, css_editor_bounds, css_editor_files } = user_config;
this.settings = defaultSettings.map(set => {
const newSet = new SettingsSet(set);
@ -48,6 +48,7 @@ export default new class Settings {
CssEditor.updateScss(scss, true);
CssEditor.editor_bounds = css_editor_bounds || {};
CssEditor.files = css_editor_files || [];
} catch (err) {
// There was an error loading settings
// This probably means that the user doesn't have any settings yet
@ -68,7 +69,8 @@ export default new class Settings {
height: CssEditor.editor_bounds.height,
x: CssEditor.editor_bounds.x,
y: CssEditor.editor_bounds.y
}
},
css_editor_files: CssEditor.files
});
for (let set of this.getSettings) {

View File

@ -13,6 +13,7 @@ import { EventEmitter } from 'events';
import { SettingUpdatedEvent, SettingsUpdatedEvent } from 'structs';
import { DOM, Modals } from 'ui';
import { Utils, FileUtils, ClientIPC, ClientLogger as Logger } from 'common';
import filewatcher from 'filewatcher';
class ThemeEvents {
constructor(theme) {
@ -45,6 +46,15 @@ export default class Theme {
this.settings.on('setting-updated', event => this.events.emit('setting-updated', event));
this.settings.on('settings-updated', event => this.events.emit('settings-updated', event));
this.settings.on('settings-updated', event => this.recompile());
this.filewatcher = filewatcher();
const files = this.files;
this.data.files = [];
this.files = files;
this.filewatcher.on('change', (file, stat) => {
// Recompile SCSS
this.recompile();
});
}
get configs() { return this.__themeInternals.configs }
@ -68,7 +78,8 @@ export default class Theme {
get settings() { return this.userConfig.config }
get config() { return this.settings.settings }
get themeConfig() { return this.config }
get css() { return this.userConfig.css }
get data() { return this.userConfig.data || (this.userConfig.data = {}) }
get css() { return this.data.css }
get events() { return this.EventEmitter ? this.EventEmitter : (this.EventEmitter = new ThemeEvents(this)) }
showSettingsModal() {
@ -90,7 +101,7 @@ export default class Theme {
await FileUtils.writeFile(`${this.themePath}/user.config.json`, JSON.stringify({
enabled: this.enabled,
config: this.settings.strip().settings,
css: this.css
data: this.data
}));
this.settings.setSaved();
@ -116,29 +127,36 @@ export default class Theme {
async compile() {
console.log('Compiling CSS');
let css = '';
if (this.info.type === 'sass') {
const config = await ThemeManager.getConfigAsSCSS(this.settings);
css = await ClientIPC.send('bd-compileSass', {
const result = await ClientIPC.send('bd-compileSass', {
data: config,
path: this.paths.mainPath.replace(/\\/g, '/')
});
console.log('SCSS compiler result:', result);
Logger.log(this.name, ['Finished compiling theme', new class Info {
get SCSS_variables() { console.log(config); }
get Compiled_SCSS() { console.log(css); }
get Result() { console.log(result); }
}]);
} else {
css = await FileUtils.readFile(this.paths.mainPath);
}
return css;
return {
css: result.css.toString(),
files: result.stats.includedFiles
};
} else {
return {
css: FileUtils.readFile(this.paths.mainPath)
};
}
}
async recompile() {
const css = await this.compile();
this.userConfig.css = css;
const data = await this.compile();
this.data.css = data.css;
this.files = data.files;
await this.saveConfiguration();
@ -148,4 +166,37 @@ export default class Theme {
}
}
/**
* An array of files that are being watched for changes.
* @returns {Array} Files being watched
*/
get files() {
return this.data.files || (this.data.files = []);
}
/**
* Sets all files to be watched.
* @param {Array} files Files to watch
*/
set files(files) {
if (!files) files = [];
for (let file of files) {
if (!this.files.includes(file)) {
this.filewatcher.add(file);
this.files.push(file);
Logger.log(this.name, `Watching file ${file} for changes`);
}
}
for (let index in this.files) {
const file = this.files[index];
if (!files.includes(file)) {
this.filewatcher.remove(file);
this.files.splice(index, 1);
Logger.log(this.name, `No longer watching file ${file} for changes`);
}
}
}
}

View File

@ -84,7 +84,7 @@ class Comms {
o.reply({ err });
return;
}
o.reply(result.css.toString());
o.reply(result);
});
});
}

15
package-lock.json generated
View File

@ -2423,6 +2423,12 @@
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
"dev": true
},
"debounce": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.1.0.tgz",
"integrity": "sha512-ZQVKfRVlwRfD150ndzEK8M90ABT+Y/JQKs4Y7U4MXdpuoUkkrr4DwKbVux3YjylA5bUMUj0Nc3pMxPJX6N2QQQ==",
"dev": true
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -3439,6 +3445,15 @@
"integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=",
"dev": true
},
"filewatcher": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/filewatcher/-/filewatcher-3.0.1.tgz",
"integrity": "sha1-9KGVc1Xdr0Q8zXiolfPVXiPIoDQ=",
"dev": true,
"requires": {
"debounce": "1.1.0"
}
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",

View File

@ -28,6 +28,7 @@
"eslint": "^4.16.0",
"eslint-plugin-vue": "^4.3.0",
"file-type": "^7.6.0",
"filewatcher": "^3.0.1",
"gulp": "^3.9.1",
"gulp-babel": "^7.0.0",
"gulp-plumber": "^1.2.0",

View File

@ -0,0 +1,6 @@
// Import this file in the custom CSS editor with
// @import '{path-to-betterdiscord}/tests/example-scss-file.scss';
// and when it is changed your custom CSS will be recompiled
.bd-settings {
height: 50%;
}