CSS editor improvements

This commit is contained in:
Samuel Elliott 2018-03-03 23:36:17 +00:00
parent 976aecd8f2
commit 91275f4332
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
18 changed files with 390 additions and 110 deletions

View File

@ -76,6 +76,32 @@
"headertext": "Emote Settings", "headertext": "Emote Settings",
"settings": [] "settings": []
}, },
{
"id": "css",
"text": "CSS Editor",
"hidden": true,
"settings": [
{
"category": "default",
"settings": [
{
"id": "live-update",
"type": "bool",
"text": "Live update",
"hint": "Automatically recompile custom CSS when typing in the custom CSS editor.",
"value": true
},
{
"id": "watch-files",
"type": "bool",
"text": "Watch included files",
"hint": "Automatically recompile theme and custom CSS when a file it imports is changed.",
"value": true
}
]
}
]
},
{ {
"id": "security", "id": "security",
"text": "Security", "text": "Security",

View File

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

View File

@ -8,20 +8,33 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import { ClientIPC } from 'common'; import { FileUtils, ClientLogger as Logger, ClientIPC } from 'common';
import Settings from './settings'; import Settings from './settings';
import { DOM } from 'ui'; import { DOM } from 'ui';
import filewatcher from 'filewatcher'; import filewatcher from 'filewatcher';
import path from 'path';
import electron from 'electron';
/** /**
* Custom css editor communications * Custom css editor communications
*/ */
export default class { export default new class {
constructor() {
this._scss = '';
this._css = '';
this._error = undefined;
this.editor_bounds = undefined;
this._files = undefined;
this._filewatcher = undefined;
this._watchfiles = undefined;
this.compiling = false;
}
/** /**
* Init css editor * Init css editor
*/ */
static init() { init() {
ClientIPC.on('bd-get-scss', () => this.sendToEditor('set-scss', { scss: this.scss })); ClientIPC.on('bd-get-scss', () => this.sendToEditor('set-scss', { scss: this.scss }));
ClientIPC.on('bd-update-scss', (e, scss) => this.updateScss(scss)); ClientIPC.on('bd-update-scss', (e, scss) => this.updateScss(scss));
ClientIPC.on('bd-save-csseditor-bounds', (e, bounds) => this.saveEditorBounds(bounds)); ClientIPC.on('bd-save-csseditor-bounds', (e, bounds) => this.saveEditorBounds(bounds));
@ -31,17 +44,25 @@ export default class {
await this.save(); await this.save();
}); });
this.filewatcher = filewatcher(); this.liveupdate = Settings.getSetting('css', 'default', 'live-update');
this.filewatcher.on('change', (file, stat) => { this.liveupdate.on('setting-updated', event => {
// Recompile SCSS this.sendToEditor('set-liveupdate', event.value);
this.updateScss(this.scss); });
ClientIPC.on('bd-get-liveupdate', () => this.sendToEditor('set-liveupdate', this.liveupdate.value));
ClientIPC.on('bd-set-liveupdate', (e, value) => this.liveupdate.value = value);
this.watchfilessetting = Settings.getSetting('css', 'default', 'watch-files');
this.watchfilessetting.on('setting-updated', event => {
if (event.value) this.watchfiles = this.files;
else this.watchfiles = [];
}); });
} }
/** /**
* Show css editor, flashes if already visible * Show css editor, flashes if already visible
*/ */
static async show() { async show() {
await ClientIPC.send('openCssEditor', this.editor_bounds); await ClientIPC.send('openCssEditor', this.editor_bounds);
} }
@ -50,34 +71,35 @@ export default class {
* @param {String} scss scss to compile * @param {String} scss scss to compile
* @param {bool} sendSource send to css editor instance * @param {bool} sendSource send to css editor instance
*/ */
static updateScss(scss, sendSource) { async updateScss(scss, sendSource) {
if (sendSource) if (sendSource)
this.sendToEditor('set-scss', { scss }); this.sendToEditor('set-scss', { scss });
if (!scss) { if (!scss) {
this._scss = this.css = ''; this._scss = this.css = '';
this.sendToEditor('scss-error', null); this.sendToEditor('scss-error', null);
return Promise.resolve(); return;
} }
return new Promise((resolve, reject) => { try {
this.compile(scss).then(result => { this.compiling = true;
this.css = result.css.toString(); const result = await this.compile(scss);
this._scss = scss; this.css = result.css.toString();
this.files = result.stats.includedFiles; this._scss = scss;
this.sendToEditor('scss-error', null); this.files = result.stats.includedFiles;
resolve(); this.error = null;
}).catch(err => { this.compiling = false;
this.sendToEditor('scss-error', err); } catch (err) {
reject(err); this.compiling = false;
}); this.error = err;
}); throw err;
}
} }
/** /**
* Save css to file * Save css to file
*/ */
static async save() { async save() {
Settings.saveSettings(); Settings.saveSettings();
} }
@ -85,7 +107,7 @@ export default class {
* Save current editor bounds * Save current editor bounds
* @param {Rectangle} bounds editor bounds * @param {Rectangle} bounds editor bounds
*/ */
static saveEditorBounds(bounds) { saveEditorBounds(bounds) {
this.editor_bounds = bounds; this.editor_bounds = bounds;
Settings.saveSettings(); Settings.saveSettings();
} }
@ -94,70 +116,192 @@ export default class {
* Send scss to core for compilation * Send scss to core for compilation
* @param {String} scss scss string * @param {String} scss scss string
*/ */
static async compile(scss) { async compile(scss) {
const result = await ClientIPC.send('bd-compileSass', { data: scss }); return await ClientIPC.send('bd-compileSass', {
console.log('Custom CSS SCSS compiler result:', result, '- CSS:', result.css.toString()); data: scss,
return result; path: await this.fileExists() ? this.filePath : undefined
});
} }
/** /**
* Send css to open editor * Recompile the current SCSS
* @return {Promise}
*/
async recompile() {
return await this.updateScss(this.scss);
}
/**
* Send data to open editor
* @param {any} channel * @param {any} channel
* @param {any} data * @param {any} data
*/ */
static async sendToEditor(channel, data) { async sendToEditor(channel, data) {
return await ClientIPC.send('sendToCssEditor', { channel, data }); return await ClientIPC.send('sendToCssEditor', { channel, data });
} }
/**
* Opens an SCSS file in a system editor
*/
async openSystemEditor() {
try {
await FileUtils.fileExists(this.filePath);
} catch (err) {
// File doesn't exist
// Create it
await FileUtils.writeFile(this.filePath, '');
}
Logger.log('CSS Editor', `Opening file ${this.filePath} in the user's default editor.`);
// For some reason this doesn't work
// if (!electron.shell.openItem(this.filePath))
if (!electron.shell.openExternal('file://' + this.filePath))
throw {message: 'Failed to open system editor.'};
}
/** Set current state
* @param {String} scss Current uncompiled SCSS
* @param {String} css Current compiled CSS
* @param {String} files Files imported in the SCSS
* @param {String} err Current compiler error
*/
setState(scss, css, files, err) {
this._scss = scss;
this.sendToEditor('set-scss', { scss });
this.css = css;
this.files = files;
this.error = err;
}
/** /**
* Current uncompiled scss * Current uncompiled scss
*/ */
static get scss() { get scss() {
return this._scss || ''; return this._scss || '';
} }
/** /**
* Set current scss * Set current scss
*/ */
static set scss(scss) { set scss(scss) {
this.updateScss(scss, true); this.updateScss(scss, true);
} }
/**
* Current compiled css
*/
get css() {
return this._css || '';
}
/** /**
* Inject compiled css to head * Inject compiled css to head
* {DOM} * {DOM}
*/ */
static set css(css) { set css(css) {
this._css = css;
DOM.injectStyle(css, 'bd-customcss'); DOM.injectStyle(css, 'bd-customcss');
} }
/** /**
* An array of files that are being watched for changes. * Current error
* @returns {Array} Files being watched
*/ */
static get files() { get error() {
return this._error || undefined;
}
/**
* Set current error
* {DOM}
*/
set error(err) {
this._error = err;
this.sendToEditor('scss-error', err);
}
/**
* An array of files that are imported in custom CSS.
* @return {Array} Files being watched
*/
get files() {
return this._files || (this._files = []); return this._files || (this._files = []);
} }
/**
* Sets all files that are imported in custom CSS.
* @param {Array} files Files to watch
*/
set files(files) {
this._files = files;
if (Settings.get('css', 'default', 'watch-files'))
this.watchfiles = files;
}
/**
* A filewatcher instance.
*/
get filewatcher() {
if (this._filewatcher) return this._filewatcher;
this._filewatcher = filewatcher();
this._filewatcher.on('change', (file, stat) => {
// Recompile SCSS
this.recompile();
});
return this._filewatcher;
}
/**
* An array of files that are being watched for changes.
* @return {Array} Files being watched
*/
get watchfiles() {
return this._watchfiles || (this._watchfiles = []);
}
/** /**
* Sets all files to be watched. * Sets all files to be watched.
* @param {Array} files Files to watch * @param {Array} files Files to watch
*/ */
static set files(files) { set watchfiles(files) {
for (let file of files) { for (let file of files) {
if (!this.files.includes(file)) { if (!this.watchfiles.includes(file)) {
this.filewatcher.add(file); this.filewatcher.add(file);
this.files.push(file); this.watchfiles.push(file);
Logger.log('CSS Editor', `Watching file ${file} for changes`);
} }
} }
for (let index in this.files) { for (let index in this.watchfiles) {
const file = this.files[index]; let file = this.watchfiles[index];
if (!files.includes(file)) { while (file && !files.find(f => f === file)) {
this.filewatcher.remove(file); this.filewatcher.remove(file);
this.files.splice(index, 1); this.watchfiles.splice(index, 1);
Logger.log('CSS Editor', `No longer watching file ${file} for changes`);
file = this.watchfiles[index];
} }
} }
} }
/**
* The path of the file the system editor should save to.
* @return {String}
*/
get filePath() {
return path.join(Settings.dataPath, 'user.scss');
}
/**
* Checks if the system editor's file exists.
* @return {Boolean}
*/
async fileExists() {
try {
await FileUtils.fileExists(this.filePath);
return true;
} catch (err) {
return false;
}
}
} }

View File

@ -56,6 +56,7 @@ export default class ExtModule {
get dirName() { return this.paths.dirName } get dirName() { return this.paths.dirName }
get enabled() { return true } get enabled() { return true }
get config() { return this.userConfig.config || [] } get config() { return this.userConfig.config || [] }
get data() { return this.userConfig.data || (this.userConfig.data = {}) }
get events() { return this.EventEmitter ? this.EventEmitter : (this.EventEmitter = new ExtModuleEvents(this)) } get events() { return this.EventEmitter ? this.EventEmitter : (this.EventEmitter = new ExtModuleEvents(this)) }
} }

View File

@ -68,7 +68,7 @@ export default class Plugin {
get settings() { return this.userConfig.config } get settings() { return this.userConfig.config }
get config() { return this.settings.settings } get config() { return this.settings.settings }
get pluginConfig() { return this.config } get pluginConfig() { return this.config }
get data() { return this.userConfig.data } get data() { return this.userConfig.data || (this.userConfig.data = {}) }
get exports() { return this._exports ? this._exports : (this._exports = this.getExports()) } get exports() { return this._exports ? this._exports : (this._exports = this.getExports()) }
get events() { return this.EventEmitter ? this.EventEmitter : (this.EventEmitter = new PluginEvents(this)) } get events() { return this.EventEmitter ? this.EventEmitter : (this.EventEmitter = new PluginEvents(this)) }

View File

@ -105,6 +105,9 @@ export default class PluginApi {
getConfigAsSCSS(settingsset) { getConfigAsSCSS(settingsset) {
return ThemeManager.getConfigAsSCSS(settingsset ? settingsset : this.plugin.settings); return ThemeManager.getConfigAsSCSS(settingsset ? settingsset : this.plugin.settings);
} }
getConfigAsSCSSMap(settingsset) {
return ThemeManager.getConfigAsSCSSMap(settingsset ? settingsset : this.plugin.settings);
}
injectStyle(id, css) { injectStyle(id, css) {
if (id && !css) css = id, id = undefined; if (id && !css) css = id, id = undefined;
this.deleteStyle(id); this.deleteStyle(id);
@ -132,6 +135,7 @@ export default class PluginApi {
return { return {
compileSass: this.compileSass.bind(this), compileSass: this.compileSass.bind(this),
getConfigAsSCSS: this.getConfigAsSCSS.bind(this), getConfigAsSCSS: this.getConfigAsSCSS.bind(this),
getConfigAsSCSSMap: this.getConfigAsSCSSMap.bind(this),
injectStyle: this.injectStyle.bind(this), injectStyle: this.injectStyle.bind(this),
injectSass: this.injectSass.bind(this), injectSass: this.injectSass.bind(this),
deleteStyle: this.deleteStyle.bind(this), deleteStyle: this.deleteStyle.bind(this),

View File

@ -27,7 +27,7 @@ export default new class Settings {
const settingsPath = path.resolve(this.dataPath, 'user.settings.json'); const settingsPath = path.resolve(this.dataPath, 'user.settings.json');
const user_config = await FileUtils.readJsonFromFile(settingsPath); const user_config = await FileUtils.readJsonFromFile(settingsPath);
const { settings, scss, css_editor_bounds, css_editor_files } = user_config; const { settings, scss, css, css_editor_files, scss_error, css_editor_bounds } = user_config;
this.settings = defaultSettings.map(set => { this.settings = defaultSettings.map(set => {
const newSet = new SettingsSet(set); const newSet = new SettingsSet(set);
@ -46,9 +46,8 @@ export default new class Settings {
return newSet; return newSet;
}); });
CssEditor.updateScss(scss, true); CssEditor.setState(scss, css, css_editor_files, scss_error);
CssEditor.editor_bounds = css_editor_bounds || {}; CssEditor.editor_bounds = css_editor_bounds || {};
CssEditor.files = css_editor_files || [];
} catch (err) { } catch (err) {
// There was an error loading settings // There was an error loading settings
// This probably means that the user doesn't have any settings yet // This probably means that the user doesn't have any settings yet
@ -64,13 +63,15 @@ export default new class Settings {
await FileUtils.writeJsonToFile(settingsPath, { await FileUtils.writeJsonToFile(settingsPath, {
settings: this.settings.map(set => set.strip()), settings: this.settings.map(set => set.strip()),
scss: CssEditor.scss, scss: CssEditor.scss,
css: CssEditor.css,
css_editor_files: CssEditor.files,
scss_error: CssEditor.error,
css_editor_bounds: { css_editor_bounds: {
width: CssEditor.editor_bounds.width, width: CssEditor.editor_bounds.width,
height: CssEditor.editor_bounds.height, height: CssEditor.editor_bounds.height,
x: CssEditor.editor_bounds.x, x: CssEditor.editor_bounds.x,
y: CssEditor.editor_bounds.y y: CssEditor.editor_bounds.y
}, }
css_editor_files: CssEditor.files
}); });
for (let set of this.getSettings) { for (let set of this.getSettings) {
@ -90,6 +91,7 @@ export default new class Settings {
get core() { return this.getSet('core') } get core() { return this.getSet('core') }
get ui() { return this.getSet('ui') } get ui() { return this.getSet('ui') }
get emotes() { return this.getSet('emotes') } get emotes() { return this.getSet('emotes') }
get css() { return this.getSet('css') }
get security() { return this.getSet('security') } get security() { return this.getSet('security') }
getCategory(set_id, category_id) { getCategory(set_id, category_id) {

View File

@ -8,6 +8,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import Settings from './settings';
import ThemeManager from './thememanager'; import ThemeManager from './thememanager';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { SettingUpdatedEvent, SettingsUpdatedEvent } from 'structs'; import { SettingUpdatedEvent, SettingsUpdatedEvent } from 'structs';
@ -47,13 +48,11 @@ export default class Theme {
this.settings.on('settings-updated', event => this.events.emit('settings-updated', event)); this.settings.on('settings-updated', event => this.events.emit('settings-updated', event));
this.settings.on('settings-updated', event => this.recompile()); this.settings.on('settings-updated', event => this.recompile());
this.filewatcher = filewatcher(); const watchfiles = Settings.getSetting('css', 'default', 'watch-files');
const files = this.files; if (watchfiles.value) this.watchfiles = this.files;
this.data.files = []; watchfiles.on('setting-updated', event => {
this.files = files; if (event.value) this.watchfiles = this.files;
this.filewatcher.on('change', (file, stat) => { else this.watchfiles = [];
// Recompile SCSS
this.recompile();
}); });
} }
@ -166,34 +165,64 @@ export default class Theme {
} }
/** /**
* An array of files that are being watched for changes. * An array of files that are imported in custom CSS.
* @returns {Array} Files being watched * @return {Array} Files being watched
*/ */
get files() { get files() {
return this.data.files || (this.data.files = []); return this.data.files || (this.data.files = []);
} }
/** /**
* Sets all files to be watched. * Sets all files that are imported in custom CSS.
* @param {Array} files Files to watch * @param {Array} files Files to watch
*/ */
set files(files) { set files(files) {
if (!files) files = []; this.data.files = files;
if (Settings.get('css', 'default', 'watch-files'))
this.watchfiles = files;
}
/**
* A filewatcher instance.
*/
get filewatcher() {
if (this._filewatcher) return this._filewatcher;
this._filewatcher = filewatcher();
this._filewatcher.on('change', (file, stat) => {
// Recompile SCSS
this.recompile();
});
return this._filewatcher;
}
/**
* An array of files that are being watched for changes.
* @return {Array} Files being watched
*/
get watchfiles() {
return this._watchfiles || (this._watchfiles = []);
}
/**
* Sets all files to be watched.
* @param {Array} files Files to watch
*/
set watchfiles(files) {
for (let file of files) { for (let file of files) {
if (!this.files.includes(file)) { if (!this.watchfiles.includes(file)) {
this.filewatcher.add(file); this.filewatcher.add(file);
this.files.push(file); this.watchfiles.push(file);
Logger.log(this.name, `Watching file ${file} for changes`); Logger.log(this.name, `Watching file ${file} for changes`);
} }
} }
for (let index in this.files) { for (let index in this.watchfiles) {
const file = this.files[index]; let file = this.watchfiles[index];
if (!files.includes(file)) { while (file && !files.find(f => f === file)) {
this.filewatcher.remove(file); this.filewatcher.remove(file);
this.files.splice(index, 1); this.watchfiles.splice(index, 1);
Logger.log(this.name, `No longer watching file ${file} for changes`); Logger.log(this.name, `No longer watching file ${file} for changes`);
file = this.watchfiles[index];
} }
} }
} }

View File

@ -50,6 +50,10 @@ export default class SettingsSet {
return this.args.headertext || `${this.text} Settings`; return this.args.headertext || `${this.text} Settings`;
} }
get hidden() {
return this.args.hidden || false;
}
get categories() { get categories() {
return this.args.settings || []; return this.args.settings || [];
} }

View File

@ -23,10 +23,8 @@
.bd-hint { .bd-hint {
flex: 1 1 auto; flex: 1 1 auto;
color: #72767d;
font-size: 14px;
font-weight: 500;
margin-top: 5px; margin-top: 5px;
margin-bottom: 0;
line-height: 20px; line-height: 20px;
border-bottom: 0px solid rgba(114, 118, 126, 0.1); border-bottom: 0px solid rgba(114, 118, 126, 0.1);
} }

View File

@ -0,0 +1,15 @@
.bd-err {
color: $colerr;
}
.bd-p {
color: $coldimwhite;
font-size: 14px;
font-weight: 500;
margin: 10px 0;
}
.bd-hint {
@extend .bd-p;
color: #72767d;
}

View File

@ -10,3 +10,4 @@
@import './profilebadges.scss'; @import './profilebadges.scss';
@import './discordoverrides.scss'; @import './discordoverrides.scss';
@import './helpers.scss';

View File

@ -31,7 +31,7 @@
</div> </div>
</div> </div>
<ContentColumn slot="content"> <ContentColumn slot="content">
<div v-for="set in Settings.settings" v-if="activeContent(set.id) || animatingContent(set.id)" :class="{active: activeContent(set.id), animating: animatingContent(set.id)}"> <div v-for="set in Settings.settings" v-if="!set.hidden && activeContent(set.id) || animatingContent(set.id)" :class="{active: activeContent(set.id), animating: animatingContent(set.id)}">
<SettingsWrapper :headertext="set.headertext"> <SettingsWrapper :headertext="set.headertext">
<SettingsPanel :settings="set" :schemes="set.schemes" /> <SettingsPanel :settings="set" :schemes="set.schemes" />
</SettingsWrapper> </SettingsWrapper>

View File

@ -11,58 +11,97 @@
<template> <template>
<SettingsWrapper headertext="CSS Editor"> <SettingsWrapper headertext="CSS Editor">
<div class="bd-css-editor"> <div class="bd-css-editor">
<div v-if="CssEditor.error" class="bd-form-item">
<h5 style="margin-bottom: 10px;">Compiler error</h5>
<div class="bd-err bd-pre-wrap"><div class="bd-pre">{{ CssEditor.error.formatted }}</div></div>
<div class="bd-form-divider"></div>
</div>
<div class="bd-form-item"> <div class="bd-form-item">
<h5>Custom Editor</h5> <h5>Custom Editor</h5>
<div class="bd-form-warning"> <FormButton v-if="internalEditorIsInstalled" :onClick="openInternalEditor">Open</FormButton>
<div class="bd-text">Custom Editor is not installed!</div> <template v-else>
<FormButton>Install</FormButton> <div class="bd-form-warning">
</div> <div class="bd-text">Custom Editor is not installed!</div>
<span style="color: #FFF; font-size: 12px; font-weight: 700;">*This is displayed if editor is not installed</span> <FormButton>Install</FormButton>
<FormButton :onClick="openInternalEditor">Open</FormButton> </div>
<span style="color: #fff; font-size: 12px; font-weight: 700;">* This is displayed if editor is not installed</span>
</template>
</div> </div>
<div class="bd-form-divider"></div> <div class="bd-form-divider"></div>
<Setting :setting="liveUpdateSetting" :change="enabled => liveUpdateSetting.value = enabled" />
<div class="bd-form-item"> <div class="bd-form-item">
<h5>System Editor</h5> <h5>System Editor</h5>
<FormButton> <FormButton :onClick="openSystemEditor">Open</FormButton>
Open <p class="bd-hint">This will open {{ systemEditorPath }} in your system's default editor.</p>
</FormButton>
</div> </div>
<div class="bd-form-divider"></div> <div class="bd-form-divider"></div>
<FormButton :onClick="() => {}">Enabled</FormButton>
<FormButton :disabled="true"><span>Disabled</span></FormButton> <div class="bd-form-item">
<FormButton :loading="true" /> <h5 style="margin-bottom: 10px;">Settings</h5>
</div>
<SettingsPanel :settings="settingsset" />
<!-- <Setting :setting="liveUpdateSetting" />
<Setting :setting="watchFilesSetting" /> -->
<FormButton :onClick="recompile" :loading="compiling">Recompile</FormButton>
</div> </div>
</SettingsWrapper> </SettingsWrapper>
</template> </template>
<script> <script>
// Imports // Imports
import { CssEditor } from 'modules'; import { Settings, CssEditor } from 'modules';
import { SettingsWrapper } from './'; import { SettingsWrapper } from './';
import { FormButton } from '../common'; import { FormButton } from '../common';
import SettingsPanel from './SettingsPanel.vue';
import Setting from './setting/Setting.vue'; import Setting from './setting/Setting.vue';
export default { export default {
components: { components: {
SettingsWrapper, SettingsWrapper,
SettingsPanel,
Setting, Setting,
FormButton FormButton
}, },
data() { data() {
return { return {
liveUpdateSetting: { CssEditor
id: "live-update", };
type: "bool", },
text: "Live Update", computed: {
hint: "Automatically update client css when saved.", error() {
value: true return this.CssEditor.error;
} },
compiling() {
return this.CssEditor.compiling;
},
systemEditorPath() {
return this.CssEditor.filePath;
},
liveUpdateSetting() {
return Settings.getSetting('css', 'default', 'live-update');
},
watchFilesSetting() {
return Settings.getSetting('css', 'default', 'watch-files');
},
settingsset() {
return Settings.css;
},
internalEditorIsInstalled() {
return true;
} }
}, },
methods: { methods: {
openInternalEditor() { openInternalEditor() {
CssEditor.show(); this.CssEditor.show();
},
openSystemEditor() {
this.CssEditor.openSystemEditor();
},
recompile() {
this.CssEditor.recompile();
} }
} }
} }

View File

@ -116,33 +116,39 @@ export default class DOM {
return document.querySelector(e); return document.querySelector(e);
} }
static getElements(e) {
return document.querySelectorAll(e);
}
static createElement(tag = 'div', className = null, id = null) { static createElement(tag = 'div', className = null, id = null) {
return new BdNode(tag, className, id); return new BdNode(tag, className, id);
} }
static deleteStyle(id) { static deleteStyle(id) {
const exists = this.getElement(`bd-styles > #${id}`); const exists = Array.from(this.bdStyles.children).find(e => e.id === id);
if (exists) exists.remove(); if (exists) exists.remove();
} }
static injectStyle(css, id) { static injectStyle(css, id) {
this.deleteStyle(id); const style = Array.from(this.bdStyles.children).find(e => e.id === id) || this.createElement('style', null, id).element;
this.bdStyles.append(this.createStyle(css, id)); style.textContent = css;
this.bdStyles.append(style);
} }
static getStyleCss(id) { static getStyleCss(id) {
const exists = this.getElement(`bd-styles > #${id}`); const exists = this.bdStyles.children.find(e => e.id === id);
return exists ? exists.textContent : ''; return exists ? exists.textContent : '';
} }
static deleteTheme(id) { static deleteTheme(id) {
const exists = this.getElement(`bd-themes > #${id}`); const exists = Array.from(this.bdThemes.children).find(e => e.id === id);
if (exists) exists.remove(); if (exists) exists.remove();
} }
static injectTheme(css, id) { static injectTheme(css, id) {
this.deleteTheme(id); const style = Array.from(this.bdThemes.children).find(e => e.id === id) || this.createElement('style', null, id).element;
this.bdThemes.append(this.createStyle(css, id)); style.textContent = css;
this.bdThemes.append(style);
} }
static createStyle(css, id) { static createStyle(css, id) {

View File

@ -25,7 +25,7 @@
<button @click="update">Update</button> <button @click="update">Update</button>
<div class="flex-spacer"></div> <div class="flex-spacer"></div>
<div id="chkboxLiveUpdate"> <div id="chkboxLiveUpdate">
<input type="checkbox" @click="toggleLiveUpdate" :checked="liveUpdate" /><span>Live Update</span> <label><input type="checkbox" @click="toggleLiveUpdate" v-model="liveUpdate" /><span>Live Update</span></label>
</div> </div>
</div> </div>
</div> </div>
@ -130,8 +130,7 @@
return this.$refs.mycm; return this.$refs.mycm;
} }
}, },
mounted() { created() {
this.codemirror.on('keyup', this.cmOnKeyUp);
BDIpc.on('set-scss', (_, data) => { BDIpc.on('set-scss', (_, data) => {
if (data.error) { if (data.error) {
console.log(data.error); console.log(data.error);
@ -141,14 +140,24 @@
this.setScss(data.scss); this.setScss(data.scss);
}); });
BDIpc.sendToDiscord('get-scss');
BDIpc.on('scss-error', (_, err) => { BDIpc.on('scss-error', (_, err) => {
this.error = err; this.error = err;
this.$forceUpdate(); this.$forceUpdate();
if (err) if (err)
console.error('SCSS parse error:', err); console.error('SCSS parse error:', err);
}); });
BDIpc.on('set-liveupdate', (e, liveUpdate) => this.liveUpdate = liveUpdate);
},
mounted() {
this.codemirror.on('keyup', this.cmOnKeyUp);
BDIpc.sendToDiscord('get-scss');
BDIpc.sendToDiscord('get-liveupdate');
},
watch: {
liveUpdate(liveUpdate) {
BDIpc.sendToDiscord('set-liveupdate', liveUpdate);
}
}, },
methods: { methods: {
save() { save() {

View File

@ -38,11 +38,10 @@
width: 25px; width: 25px;
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
/*background: #263238;*/
background: #36393f; background: #36393f;
color: #bac9d2; color: #bac9d2;
font-family: Whitney,Helvetica Neue,Helvetica,Arial,sans-serif; font-family: Whitney,Helvetica Neue,Helvetica,Arial,sans-serif;
transition: background-color .2s ease; transition: background-color .2s ease, color .2s ease;
cursor: default; cursor: default;
border: 0; border: 0;
height: 25px; height: 25px;

View File

@ -3,7 +3,7 @@
background: #292b2f; background: #292b2f;
border-top: 1px solid hsla(218,5%,47%,.3); border-top: 1px solid hsla(218,5%,47%,.3);
color: #d84040; color: #d84040;
font-family: Whitney,Helvetica Neue,Helvetica,Arial,sans-serif; font-family: monospace;
white-space: pre-wrap; white-space: pre-wrap;
font-size: 12px; font-size: 12px;
} }
@ -30,7 +30,7 @@
background: #36393f; background: #36393f;
color: #bac9d2; color: #bac9d2;
font-family: Whitney,Helvetica Neue,Helvetica,Arial,sans-serif; font-family: Whitney,Helvetica Neue,Helvetica,Arial,sans-serif;
transition: background-color .2s ease; transition: background-color .2s ease, color .2s ease;
cursor: pointer; cursor: pointer;
border: 0; border: 0;
margin-right: 4px; margin-right: 4px;
@ -47,6 +47,10 @@
line-height: 22px; line-height: 22px;
flex: 0 0 auto; flex: 0 0 auto;
label {
cursor: pointer;
}
input[type="checkbox"] { input[type="checkbox"] {
margin: 0 6px 0 0; margin: 0 6px 0 0;
cursor: pointer; cursor: pointer;
@ -57,7 +61,6 @@
font-weight: 500; font-weight: 500;
color: #bac9d2; color: #bac9d2;
font-family: Whitney,Helvetica Neue,Helvetica,Arial,sans-serif; font-family: Whitney,Helvetica Neue,Helvetica,Arial,sans-serif;
cursor: default;
} }
} }
} }