Merge pull request #105 from samuelthomas2774/add-theme-config

Save theme configuration when enabled/disabled
This commit is contained in:
Alexei Stukov 2018-02-09 17:16:12 +02:00 committed by GitHub
commit f05517a0a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 200 additions and 10 deletions

View File

@ -16,6 +16,8 @@ class Theme {
constructor(themeInternals) { constructor(themeInternals) {
this.__themeInternals = 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.enable = this.enable.bind(this);
this.disable = this.disable.bind(this); this.disable = this.disable.bind(this);
} }
@ -37,13 +39,39 @@ class Theme {
get css() { return this.__themeInternals.css } get css() { return this.__themeInternals.css }
get id() { return this.name.toLowerCase().replace(/\s+/g, '-') } 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);
}
}
}
try {
await FileUtils.writeFile(`${this.themePath}/user.config.json`, JSON.stringify({ enabled: this.enabled, config: this.themeConfig }));
} catch (err) {
throw err;
}
if (this.settingsChanged) this.settingsChanged(this.themeConfig);
return this.pluginConfig;
}
enable() { enable() {
this.userConfig.enabled = true; this.userConfig.enabled = true;
this.saveSettings();
DOM.injectTheme(this.css, this.id); DOM.injectTheme(this.css, this.id);
} }
disable() { disable() {
this.userConfig.enabled = false; this.userConfig.enabled = false;
this.saveSettings();
DOM.deleteTheme(this.id); DOM.deleteTheme(this.id);
} }
@ -68,21 +96,13 @@ export default class extends ContentManager {
try { try {
const css = await FileUtils.readFile(paths.mainPath); const css = await FileUtils.readFile(paths.mainPath);
const instance = new Theme({ configs, info, main, paths: { contentPath: paths.contentPath, dirName: paths.dirName }, css }); const instance = new Theme({ configs, info, main, paths: { contentPath: paths.contentPath, dirName: paths.dirName }, css });
if (instance.enabled) instance.enable();
return instance; return instance;
} catch (err) { } catch (err) {
throw err; throw err;
} }
} }
static async loadPlugin(paths, configs, info, main) {
const plugin = window.require(paths.mainPath)(Plugin, {}, {});
const instance = new plugin({ configs, info, main, paths: { contentPath: paths.contentPath, dirName: paths.dirName } });
if (instance.enabled) instance.start();
return instance;
}
static enableTheme(theme) { static enableTheme(theme) {
theme.enable(); theme.enable();
} }

View File

@ -0,0 +1,134 @@
/**
* BetterDiscord Theme Settings Modal Component
* 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.
*/
<template>
<div class="bd-plugin-settings-modal" :class="{'bd-edited': changed}">
<div class="bd-backdrop" @click="attemptToClose" :class="{'bd-backdrop-out': closing}"></div>
<Modal :headerText="theme.name + ' Settings'" :close="attemptToClose" :class="{'bd-modal-out': closing}">
<div slot="body" class="bd-plugin-settings-body">
<template v-for="category in configCache">
<div v-if="category.category === 'default' || !category.type">
<PluginSetting v-for="setting in category.settings" :key="setting.id" :setting="setting" :change="settingChange" :changed="setting.changed"/>
</div>
<div v-else-if="category.type === 'static'">
<div class="bd-form-header">
<span class="bd-form-header-text">{{category.category}} static with header</span>
</div>
<PluginSetting v-for="setting in category.settings" :key="setting.id" :setting="setting" :change="settingChange" :changed="setting.changed"/>
</div>
<Drawer v-else-if="category.type === 'drawer'" :label="category.category + ' drawer'">
<PluginSetting v-for="setting in category.settings" :key="setting.id" :setting="setting" :change="settingChange" :changed="setting.changed"/>
</Drawer>
<div v-else>
<PluginSetting v-for="setting in category.settings" :key="setting.id" :setting="setting" :change="settingChange" :changed="setting.changed"/>
</div>
</template>
</div>
<div slot="footer" class="bd-footer-alert" :class ="{'bd-active': changed, 'bd-warn': warnclose}">
<div class="bd-footer-alert-text">Unsaved changes</div>
<div class="bd-button bd-reset-button bd-tp" :class="{'bd-disabled': saving}" @click="resetSettings">Reset</div>
<div class="bd-button bd-ok" :class="{'bd-disabled': saving}" @click="saveSettings">
<div v-if="saving" class="bd-spinner-7"></div>
<template v-else>Save Changes</template>
</div>
</div>
</Modal>
</div>
</template>
<script>
// Imports
import { Modal } from '../common';
import PluginSetting from './pluginsetting/PluginSetting.vue';
import Drawer from '../common/Drawer.vue';
export default {
props: ['theme', 'close'],
data() {
return {
changed: false,
warnclose: false,
configCache: [],
closing: false,
saving: false
}
},
components: {
Modal,
PluginSetting,
Drawer
},
methods: {
checkForChanges() {
let changed = false;
for (let category of this.configCache) {
const cat = this.theme.themeConfig.find(c => c.category === category.category);
for (let setting of category.settings) {
if (cat.settings.find(s => s.id === setting.id).value !== setting.value) {
changed = true;
setting.changed = true;
} else {
setting.changed = false;
}
}
}
return changed;
},
settingChange(settingId, newValue) {
for (let category of this.configCache) {
const found = category.settings.find(s => s.id === settingId);
if (found) {
found.value = newValue;
break;
}
}
this.changed = this.checkForChanges();
this.$forceUpdate();
},
async saveSettings() {
if (this.saving) return;
this.saving = true;
try {
await this.theme.saveSettings(this.configCache);
this.configCache = JSON.parse(JSON.stringify(this.theme.themeConfig));
this.changed = false;
} catch (err) {
// TODO Display error that settings failed to save
console.log(err);
}
this.saving = false;
},
resetSettings() {
if (this.saving) return;
this.configCache = JSON.parse(JSON.stringify(this.theme.themeConfig));
this.changed = false;
this.$forceUpdate();
},
attemptToClose(e) {
if (!this.changed) {
this.closing = true;
setTimeout(() => {
this.close();
}, 200);
return;
}
this.warnclose = true;
setTimeout(() => {
this.warnclose = false;
}, 400);
}
},
beforeMount() {
this.configCache = JSON.parse(JSON.stringify(this.theme.themeConfig));
console.log(this.configCache);
this.changed = this.checkForChanges();
}
}
</script>

View File

@ -32,6 +32,7 @@
<div class="bd-spinner-2"></div> <div class="bd-spinner-2"></div>
</div> </div>
</div> </div>
<ThemeSettingsModal v-if="settingsOpen !== null" :theme="settingsOpen" :close="closeSettings" />
</SettingsWrapper> </SettingsWrapper>
</template> </template>
@ -40,6 +41,7 @@
import { ThemeManager } from 'modules'; import { ThemeManager } from 'modules';
import { SettingsWrapper } from './'; import { SettingsWrapper } from './';
import { MiRefresh } from '../common'; import { MiRefresh } from '../common';
import ThemeSettingsModal from './ThemeSettingsModal.vue';
import ThemeCard from './ThemeCard.vue'; import ThemeCard from './ThemeCard.vue';
export default { export default {
@ -52,6 +54,7 @@
}, },
components: { components: {
SettingsWrapper, ThemeCard, SettingsWrapper, ThemeCard,
ThemeSettingsModal,
MiRefresh MiRefresh
}, },
methods: { methods: {

View File

@ -6,5 +6,38 @@
"description": "Example Theme 1 Description", "description": "Example Theme 1 Description",
"icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FscXVlXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg==" "icon": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FscXVlXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg=="
}, },
"main": "index.css" "main": "index.css",
"defaultConfig": [
{
"category": "default",
"settings": [
{
"id": "default-0",
"type": "text",
"value": null,
"text": "Test setting #1",
"hint": "Just some test settings to test the settings panel for themes"
},
{
"id": "default-1",
"type": "bool",
"value": true,
"text": "Work properly",
"hint": "Just some test settings to test the settings panel for themes"
},
{
"id": "default-2",
"type": "radio",
"value": "opt1",
"text": "Test Radio Setting",
"hint": "Just some test settings to test the settings panel for themes",
"options": [
{ "value": 1, "text": "Option 1" },
{ "value": 2, "text": "Option 2" },
{ "value": 3, "text": "Option 3" }
]
}
]
}
]
} }