Merge pull request #141 from samuelthomas2774/tabs-and-settings-arrays

Tabs and settings arrays
This commit is contained in:
Alexei Stukov 2018-02-21 07:52:39 +02:00 committed by GitHub
commit 99e9ce3852
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 825 additions and 217 deletions

View File

@ -135,7 +135,7 @@ export default class Theme {
let css = '';
if (this.info.type === 'sass') {
css = await ClientIPC.send('bd-compileSass', {
data: ThemeManager.getConfigAsSCSS(this.config),
data: await ThemeManager.getConfigAsSCSS(this.config),
path: this.paths.mainPath.replace(/\\/g, '/')
});
console.log(css);

View File

@ -10,6 +10,7 @@
import ContentManager from './contentmanager';
import Theme from './theme';
import { FileUtils } from 'common';
export default class ThemeManager extends ContentManager {
@ -64,36 +65,97 @@ export default class ThemeManager extends ContentManager {
theme.recompile();
}
static getConfigAsSCSS(config) {
static async getConfigAsSCSS(config) {
const variables = [];
for (let category of config) {
for (let setting of category.settings) {
variables.push(this.parseSetting(setting));
const setting_scss = await this.parseSetting(setting);
if (setting_scss) variables.push(`$${setting_scss[0]}: ${setting_scss[1]};`);
}
}
return variables.join('\n');
}
static parseSetting(setting) {
static async getConfigAsSCSSMap(config) {
const variables = [];
for (let category of config) {
for (let setting of category.settings) {
const setting_scss = await this.parseSetting(setting);
if (setting_scss) variables.push(`${setting_scss[0]}: (${setting_scss[1]})`);
}
}
return '(' + variables.join(', ') + ')';
}
static async parseSetting(setting) {
const { type, id, value } = setting;
const name = id.replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-');
if (type === 'array') {
const items = JSON.parse(JSON.stringify(value)) || [];
const settings_json = JSON.stringify(setting.settings);
for (let item of items) {
const settings = JSON.parse(settings_json);
for (let category of settings) {
const newCategory = item.settings.find(c => c.category === category.category);
for (let setting of category.settings) {
const newSetting = newCategory.settings.find(s => s.id === setting.id);
setting.value = setting.old_value = newSetting.value;
setting.changed = false;
}
}
item.settings = settings;
}
console.log('items', items);
// Final comma ensures the variable is a list
const maps = [];
for (let item of items)
maps.push(await this.getConfigAsSCSSMap(item.settings));
return [name, maps.length ? maps.join(', ') + ',' : '()'];
}
if (type === 'file' && Array.isArray(value)) {
if (!value || !value.length) return [name, '(),'];
const files = [];
for (let filepath of value) {
const buffer = await FileUtils.readFileBuffer(filepath);
const type = await FileUtils.getFileType(buffer);
files.push(`(data: ${this.toSCSSString(buffer.toString('base64'))}, type: ${this.toSCSSString(type.mime)}, url: ${this.toSCSSString(await FileUtils.toDataURI(buffer, type.mime))})`);
}
return [name, files.length ? files.join(', ') : '()'];
}
if (type === 'slider') {
return `$${name}: ${value * setting.multi || 1};`;
return [name, value * setting.multi || 1];
}
if (type === 'dropdown' || type === 'radio') {
return `$${name}: ${setting.options.find(opt => opt.id === value).value};`;
return [name, setting.options.find(opt => opt.id === value).value];
}
if (typeof value === 'boolean' || typeof value === 'number') {
return `$${name}: ${value};`;
return [name, value];
}
if (typeof value === 'string') {
return `$${name}: ${setting.scss_raw ? value : `'${setting.value.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}'`};`;
return [name, this.toSCSSString(value)];
}
}
static toSCSSString(value) {
if (typeof value !== 'string' && value.toString) value = value.toString();
return `'${typeof value === 'string' ? value.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') : ''}'`;
}
}

View File

@ -46,6 +46,12 @@
margin-top: 10px;
}
&:hover {
.bd-settings-scheme-icon {
border-color: lighten($coldimwhite, 20%);
}
}
&.bd-active {
cursor: default;

View File

@ -111,8 +111,7 @@
.platform-darwin & {
top: 0px;
.bd-sidebar-view .bd-sidebar-region,
.bd-sidebar-view .bd-content-region {
.bd-sidebar-view .bd-sidebar-region {
padding-top: 22px;
}
}

View File

@ -79,50 +79,3 @@
}
}
}
.bd-tabheader {
.bd-button {
background: transparent;
border-bottom: 2px solid rgba(114, 118, 126, 0.3);
h3 {
-webkit-user-select: none;
user-select: none;
display: block;
font-size: 1.17em;
margin-top: 1em;
margin-bottom: 1em;
margin-left: 0;
margin-right: 0;
font-weight: bold;
flex-grow: 1;
}
.bd-material-button {
width: 30px;
height: 30px;
.material-design-icon,
.bd-material-design-icon {
display: flex;
align-items: center;
fill: #FFF;
svg {
width: 24px;
height: 24px;
}
}
&:hover {
background: #2d2f34;
}
}
&:hover,
&.bd-active {
background: transparent;
border-bottom: 2px solid $colbdblue;
}
}
}

View File

@ -43,14 +43,17 @@
font-size: 12px;
}
.bd-form-item-changed .bd-form-divider {
background: $colok;
}
.bd-form-divider {
height: 1px;
margin: 15px 0;
background: hsla(218,5%,47%,.3);
transition: background-color 0.2s ease;
.bd-form-item-changed > & {
background: $colok;
}
}
.bd-form-warning {

View File

@ -0,0 +1,82 @@
.bd-form-settingsarray {
.bd-button.bd-button-primary {
padding: 3px 8px;
border-radius: 3px;
font-size: 12px;
font-weight: 400;
}
.bd-settingsarray-items {
margin-top: 15px;
.bd-settingsarray-item {
display: flex;
margin-top: 10px;
.bd-settingsarray-item-marker {
flex: 0 0 auto;
min-width: 15px;
margin-right: 5px;
color: #aaa;
font-size: 15px;
}
.bd-settingsarray-item-contents {
flex: 1 1;
}
.bd-settings-panel {
.bd-settings-categories:last-child .bd-form-item:last-child .bd-form-divider {
margin-bottom: 0;
}
.bd-settings-category:only-child > div > .bd-form-item.bd-form-item-noheader:only-child {
.bd-form-textinput,
.bd-form-numberinput {
+ .bd-form-divider {
display: none;
}
}
}
}
.bd-settingsarray-item-hint {
color: #aaa;
font-size: 15px;
font-style: italic;
word-wrap: break-word;
max-width: 385px;
}
.bd-settingsarray-item-controls {
flex: 0 0 auto;
margin-left: 5px;
}
.bd-settingsarray-open,
.bd-settingsarray-remove {
margin-left: 5px;
svg {
width: 16px;
height: 16px;
cursor: pointer;
fill: #ccc;
&:hover {
fill: #fff;
}
}
}
&:last-child .bd-settings-categories:last-child .bd-form-item:last-child .bd-form-divider {
display: none;
}
}
}
&.bd-form-settingsarray-inline .bd-settingsarray-item {
margin-top: 10px;
}
}

View File

@ -1,5 +1,10 @@
.bd-dropdown {
position: relative;
width: 100%;
h3 + & {
width: 180px;
}
.bd-dropdown-current {
color: #f6f6f7;
@ -10,7 +15,6 @@
cursor: default;
outline: none;
transition: border .15s ease;
width: 180px;
box-sizing: border-box;
display: flex;

View File

@ -5,3 +5,4 @@
@import './radios.scss';
@import './sliders.scss';
@import './switches.scss';
@import './arrays.scss';

View File

@ -5,7 +5,8 @@
.bd-form-radio,
.bd-form-numberinput,
.bd-form-slider,
.bd-setting-switch {
.bd-setting-switch,
.bd-form-settingsarray {
.bd-title {
display: flex;

View File

@ -45,6 +45,8 @@
.bd-radio-text {
flex: 1 1 auto;
color: white;
word-wrap: break-word;
width: 1px;
}
&:not(:last-child) {

View File

@ -1,5 +1,13 @@
.bd-form-textinput,
.bd-form-numberinput {
h3 {
+ .bd-textinput-wrapper,
+ .bd-textinput-wrapper input[type="text"],
+ .bd-textinput-wrapper input[type="number"] {
width: 180px;
}
}
input[type="text"],
input[type="number"] {
background: transparent;
@ -10,7 +18,7 @@
line-height: 24px;
font-size: 100%;
font-weight: 500;
width: 180px;
width: 100%;
&:focus {
color: #fff;
@ -19,6 +27,10 @@
}
}
.bd-textinput-wrapper {
width: 100%;
}
.bd-form-textarea {
.bd-form-textarea-wrap {
margin-top: 15px;

View File

@ -1,6 +1,7 @@
@import './spinners/index.scss';
@import './scrollable.scss';
@import './buttons.scss';
@import './tabs.scss';
@import './forms.scss';
@import './forms/index.scss';
@import './material-buttons.scss';

View File

@ -8,8 +8,6 @@
flex-direction: column;
overflow-y: auto;
overflow-x: hidden;
padding: 10px 10px 10px 0;
margin: 10px 0;
@include scrollbar;
}

View File

@ -0,0 +1,98 @@
.bd-tabbar {
flex: 0 0 auto;
margin-right: -15px;
display: flex;
.bd-button {
background: transparent;
border-bottom: 2px solid rgba(114, 118, 126, 0.3);
cursor: pointer;
margin-right: 15px;
padding: 15px 0;
color: #87909c;
font-size: 14px;
font-weight: 500;
transition: color 0.2s ease, border-bottom-color 0.2s ease;
flex: 0 0;
display: flex;
h3 {
flex: 0 0;
}
.bd-material-button {
margin: -1px 0 -1px 4px;
}
&:hover,
&.bd-active {
background: transparent;
border-bottom-color: $colbdblue;
color: #fff;
.bd-material-design-icon {
fill: #fff;
}
}
&.bd-active {
cursor: default;
}
}
.bd-material-button-wrap {
margin-right: 15px;
flex: 0 0;
padding: 17px 0 18px;
.bd-material-button {
margin: 0;
}
}
.bd-button,
.bd-material-button-wrap {
.bd-material-button {
width: 16px;
height: 16px;
flex: 0 0;
cursor: pointer;
.material-design-icon,
.bd-material-design-icon {
display: flex;
align-items: center;
fill: #87909c;
transition: fill 0.2s ease;
svg {
width: 16px;
height: 16px;
}
}
&:hover {
background-color: transparent;
.bd-material-design-icon {
fill: #fff;
}
}
}
}
.bd-settingswrap-header & {
margin-top: -17px;
margin-bottom: -20px;
.bd-button {
font-size: 16px;
padding: 18px 0 17px;
}
.bd-material-button {
width: 18px;
height: 18px;
}
}
}

View File

@ -85,12 +85,16 @@
.bd-modal-tip {
flex-grow: 1;
line-height: 26px;
color: #FFF;
color: #fff;
}
.bd-button {
padding: 5px 10px;
border-radius: 3px;
+ .bd-button {
margin-left: 15px;
}
}
}
}

View File

@ -11,28 +11,6 @@
flex-grow: 1;
}
.bd-settingsWrap {
display: flex;
flex-direction: column;
flex-grow: 1;
.bd-scroller-wrap {
flex-grow: 1;
}
.bd-settingsWrap-header {
color: $colbdblue;
text-transform: uppercase;
font-weight: 600;
margin-top: 10px;
margin-bottom: 20px;
font-size: 100%;
outline: 0;
padding: 0;
vertical-align: baseline;
}
}
> div:not(.active) {
opacity: 0;
position: absolute;
@ -50,8 +28,4 @@
.animating {
animation: bd-fade-out .4s forwards;
}
.bd-settingsWrap {
padding: 20px 15px 15px 15px;
}
}

View File

@ -1,3 +1,4 @@
@import './main.scss';
@import './sidebar.scss';
@import './content.scss';
@import './settingswrap.scss';

View File

@ -20,14 +20,8 @@
max-width: 310px;
min-width: 310px;
.bd-settingsWrap {
display: flex;
height: 100%;
-webkit-box-flex: 1;
flex: 1;
min-height: 1px;
box-sizing: border-box;
padding: 80px 15px 15px 15px;
.bd-scroller {
padding: 10px 10px 0 0;
}
}

View File

@ -0,0 +1,54 @@
.bd-sidebar-region {
.bd-settingswrap {
display: flex;
height: 100%;
-webkit-box-flex: 1;
flex: 1;
min-height: 1px;
box-sizing: border-box;
padding: 90px 15px 0 15px;
margin-bottom: 5px;
}
}
.bd-content-region {
.bd-settingswrap {
display: flex;
flex-direction: column;
flex-grow: 1;
> .bd-scroller-wrap {
flex-grow: 1;
> .bd-scroller {
overflow-y: scroll;
.platform-darwin .bd-settings & {
padding-top: 22px;
}
}
}
.bd-settingswrap-header {
outline: 0;
padding: 0;
margin: 30px 20px 20px;
vertical-align: baseline;
display: flex;
flex: 0 0 auto;
.bd-settingswrap-header-text {
color: $colbdblue;
text-transform: uppercase;
font-weight: 600;
font-size: 16px;
flex: 1 1 auto;
}
}
.bd-settingswrap-contents {
padding: 0 20px;
margin-bottom: 84px;
}
}
}

View File

@ -44,7 +44,7 @@
}
&.active {
color: #FFF;
color: #fff;
}
}
}

View File

@ -10,11 +10,11 @@
<template>
<div class="bd-modals-container">
<div v-for="(modal, index) in modals.stack" :key="`bd-modal-${index}`">
<div class="bd-backdrop" :class="{'bd-backdrop-out': modal.closing}" :style="{opacity: index === 0 ? undefined : 0}"></div>
<div v-for="(modal, index) in modals.stack" :key="`bd-modal-${modal.id}`">
<div class="bd-backdrop" :class="{'bd-backdrop-out': closing}" :style="{opacity: index === 0 ? undefined : 0}"></div>
<div class="bd-modal-wrap" :style="{transform: `scale(${downscale(index + 1, 0.2)})`, opacity: downscale(index + 1, 1)}">
<div class="bd-modal-close-area" @click="closeModal(modal)"></div>
<component :is="modal.component" />
<keep-alive><component :is="modal.component" /></keep-alive>
</div>
</div>
</div>
@ -37,6 +37,11 @@
eventListener: null
};
},
computed: {
closing() {
return !this.modals.stack.find(m => !m.closing);
}
},
created() {
Events.on('bd-refresh-modals', this.eventListener = () => {
this.$forceUpdate();

View File

@ -10,20 +10,12 @@
<template>
<Card :item="plugin">
<SettingSwitch v-if="plugin.type === 'plugin'" slot="toggle" :checked="plugin.enabled" :change="() => plugin.enabled ? plugin.stop() : plugin.start()" />
<SettingSwitch v-if="plugin.type === 'plugin'" slot="toggle" :checked="plugin.enabled" :change="togglePlugin" />
<ButtonGroup slot="controls">
<Button v-tooltip="'Settings'" v-if="plugin.hasSettings" :onClick="() => showSettings(plugin)">
<MiSettings size="18" />
</Button>
<Button v-tooltip="'Reload'" :onClick="() => reloadPlugin(plugin)">
<MiRefresh size="18" />
</Button>
<Button v-tooltip="'Edit'" :onClick="editPlugin">
<MiPencil size="18" />
</Button>
<Button v-tooltip="'Uninstall'" type="err">
<MiDelete size="18" />
</Button>
<Button v-tooltip="'Settings'" v-if="plugin.hasSettings" :onClick="() => showSettings(plugin)"><MiSettings size="18" /></Button>
<Button v-tooltip="'Reload'" :onClick="reloadPlugin"><MiRefresh size="18" /></Button>
<Button v-tooltip="'Edit'" :onClick="editPlugin"><MiPencil size="18" /></Button>
<Button v-tooltip="'Uninstall'" type="err"><MiDelete size="18" /></Button>
</ButtonGroup>
</Card>
</template>
@ -52,6 +44,5 @@
}
}
}
}
</script>

View File

@ -10,23 +10,20 @@
<template>
<SettingsWrapper headertext="Plugins">
<div class="bd-flex bd-flex-col bd-pluginsView">
<div class="bd-flex bd-tabheader">
<div class="bd-flex-grow bd-button" :class="{'bd-active': local}" @click="showLocal">
<h3>Local</h3>
<div class="bd-material-button" @click="refreshLocal">
<MiRefresh />
</div>
</div>
<div class="bd-flex-grow bd-button" :class="{'bd-active': !local}" @click="showOnline">
<h3>Online</h3>
<div class="bd-material-button">
<MiRefresh />
</div>
</div>
<div class="bd-tabbar" slot="header">
<div class="bd-button" :class="{'bd-active': local}" @click="showLocal">
<h3>Local</h3>
<div class="bd-material-button" v-if="local" @click="refreshLocal"><MiRefresh /></div>
</div>
<div class="bd-button" :class="{'bd-active': !local}" @click="showOnline">
<h3>Online</h3>
<div class="bd-material-button" v-if="!local" @click="refreshOnline"><MiRefresh /></div>
</div>
</div>
<div class="bd-flex bd-flex-col bd-pluginsView">
<div v-if="local" class="bd-flex bd-flex-grow bd-flex-col bd-plugins-container bd-local-plugins">
<PluginCard v-for="plugin in localPlugins" :plugin="plugin" :key="plugin.id" :togglePlugin="togglePlugin" :reloadPlugin="reloadPlugin" :showSettings="showSettings" />
<PluginCard v-for="plugin in localPlugins" :plugin="plugin" :key="plugin.id" :togglePlugin="() => togglePlugin(plugin)" :reloadPlugin="() => reloadPlugin(plugin)" :showSettings="() => showSettings(plugin)" />
</div>
<div v-if="!local" class="bd-spinner-container">
<div class="bd-spinner-2"></div>
@ -61,32 +58,31 @@
showOnline() {
this.local = false;
},
refreshLocal() {
(async () => {
await PluginManager.refreshPlugins();
})();
async refreshLocal() {
await PluginManager.refreshPlugins();
},
togglePlugin(plugin) {
async refreshOnline() {
},
async togglePlugin(plugin) {
// TODO Display error if plugin fails to start/stop
try {
if (plugin.enabled) {
PluginManager.stopPlugin(plugin);
await PluginManager.stopPlugin(plugin);
} else {
PluginManager.startPlugin(plugin);
await PluginManager.startPlugin(plugin);
}
} catch (err) {
console.log(err);
}
},
reloadPlugin(plugin) {
(async () => {
try {
await PluginManager.reloadPlugin(plugin);
this.$forceUpdate();
} catch (err) {
console.log(err);
}
})();
async reloadPlugin(plugin) {
try {
await PluginManager.reloadPlugin(plugin);
this.$forceUpdate();
} catch (err) {
console.log(err);
}
},
showSettings(plugin) {
return Modals.contentSettings(plugin);

View File

@ -58,11 +58,6 @@
Setting,
Drawer
},
data() {
return {
active_scheme: 'scheme-1'
};
},
methods: {
checkSchemeActive(scheme) {
for (let schemeCategory of scheme.settings) {

View File

@ -9,10 +9,15 @@
*/
<template>
<div class="bd-settingsWrap">
<div class="bd-settingsWrap-header">{{headertext}}</div>
<div class="bd-settingswrap">
<ScrollerWrap>
<slot />
<div class="bd-settingswrap-header">
<span class="bd-settingswrap-header-text">{{ headertext }}</span>
<slot name="header" />
</div>
<div class="bd-settingswrap-contents">
<slot />
</div>
</ScrollerWrap>
</div>
</template>

View File

@ -10,20 +10,12 @@
<template>
<Card :item="theme">
<SettingSwitch slot="toggle" :checked="theme.enabled" :change="() => theme.enabled ? theme.disable() : theme.enable()" />
<SettingSwitch slot="toggle" :checked="theme.enabled" :change="toggleTheme" />
<ButtonGroup slot="controls">
<Button v-tooltip="'Settings'" v-if="theme.hasSettings" :onClick="() => showSettings(theme)">
<MiSettings size="18" />
</Button>
<Button v-tooltip="'Reload'" :onClick="() => reloadTheme(theme)">
<MiRefresh size="18" />
</Button>
<Button v-tooltip="'Edit'" :onClick="editTheme">
<MiPencil size="18" />
</Button>
<Button v-tooltip="'Uninstall'" type="err">
<MiDelete size="18" />
</Button>
<Button v-tooltip="'Settings'" v-if="theme.hasSettings" :onClick="showSettings"><MiSettings size="18" /></Button>
<Button v-tooltip="'Reload'" :onClick="reloadTheme"><MiRefresh size="18" /></Button>
<Button v-tooltip="'Edit'" :onClick="editTheme"><MiPencil size="18" /></Button>
<Button v-tooltip="'Uninstall'" type="err"><MiDelete size="18" /></Button>
</ButtonGroup>
</Card>
</template>
@ -52,6 +44,5 @@
}
}
}
}
</script>

View File

@ -10,23 +10,20 @@
<template>
<SettingsWrapper headertext="Themes">
<div class="bd-flex bd-flex-col bd-themesView">
<div class="bd-flex bd-tabheader">
<div class="bd-flex-grow bd-button" :class="{'bd-active': local}" @click="showLocal">
<h3>Local</h3>
<div class="bd-material-button" @click="refreshLocal">
<MiRefresh />
</div>
</div>
<div class="bd-flex-grow bd-button" :class="{'bd-active': !local}" @click="showOnline">
<h3>Online</h3>
<div class="bd-material-button">
<MiRefresh />
</div>
</div>
<div class="bd-tabbar" slot="header">
<div class="bd-button" :class="{'bd-active': local}" @click="showLocal">
<h3>Local</h3>
<div class="bd-material-button" v-if="local" @click="refreshLocal"><MiRefresh /></div>
</div>
<div class="bd-button" :class="{'bd-active': !local}" @click="showOnline">
<h3>Online</h3>
<div class="bd-material-button" v-if="!local" @click="refreshOnline"><MiRefresh /></div>
</div>
</div>
<div class="bd-flex bd-flex-col bd-themesView">
<div v-if="local" class="bd-flex bd-flex-grow bd-flex-col bd-themes-container bd-local-themes">
<ThemeCard v-for="theme in localThemes" :theme="theme" :key="theme.id" :toggleTheme="toggleTheme" :reloadTheme="reloadTheme" :showSettings="showSettings" />
<ThemeCard v-for="theme in localThemes" :theme="theme" :key="theme.id" :toggleTheme="() => toggleTheme(theme)" :reloadTheme="() => reloadTheme(theme)" :showSettings="() => showSettings(theme)" />
</div>
<div v-if="!local" class="bd-spinner-container">
<div class="bd-spinner-2"></div>
@ -61,10 +58,11 @@
showOnline() {
this.local = false;
},
refreshLocal() {
(async () => {
await ThemeManager.refreshTheme();
})();
async refreshLocal() {
await ThemeManager.refreshTheme();
},
async refreshOnline() {
},
toggleTheme(theme) {
// TODO Display error if theme fails to enable/disable
@ -78,15 +76,13 @@
console.log(err);
}
},
reloadTheme(theme) {
(async () => {
try {
await ThemeManager.reloadTheme(theme);
this.$forceUpdate();
} catch (err) {
console.log(err);
}
})();
async reloadTheme(theme) {
try {
await ThemeManager.reloadTheme(theme);
this.$forceUpdate();
} catch (err) {
console.log(err);
}
},
showSettings(theme) {
return Modals.contentSettings(theme);

View File

@ -0,0 +1,32 @@
/**
* BetterDiscord Confirm 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>
<Modal :class="['bd-modal-basic', {'bd-modal-out': modal.closing}]" :headerText="modal.title" :close="modal.close">
<div slot="body" class="bd-modal-basic-body">{{ modal.text }}</div>
<div slot="footer" class="bd-modal-controls">
<div class="bd-flex-grow"></div>
<div class="bd-button" @click="modal.close">Cancel</div>
<div class="bd-button bd-ok" @click="() => { modal.confirm(); modal.close(); }">OK</div>
</div>
</Modal>
</template>
<script>
// Imports
import { Modal } from '../../common';
export default {
props: ['modal'],
components: {
Modal
}
}
</script>

View File

@ -0,0 +1,129 @@
/**
* BetterDiscord Setting File 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-form-settingsarray" :class="{'bd-form-settingsarray-inline': setting.inline}">
<div class="bd-title">
<h3>{{ setting.text }}</h3>
<button class="bd-button bd-button-primary" :class="{'bd-disabled': setting.disabled || setting.max && items.length >= setting.max}" @click="() => addItem(!setting.inline)">Add</button>
</div>
<div class="bd-hint">{{ setting.hint }}</div>
<div class="bd-settingsarray-items">
<div class="bd-settingsarray-item" v-for="(item, index) in items">
<div class="bd-settingsarray-item-marker">{{ index + 1 }}</div>
<SettingsPanel class="bd-settingsarray-item-contents" v-if="setting.inline" :settings="item.settings" :change="(c, s, v) => changeInItem(item, c, s, v)" />
<div class="bd-settingsarray-item-contents" v-else>
<div class="bd-settingsarray-item-hint">
<span v-if="item.settings[0] && item.settings[0].settings[0]">{{ item.settings[0].settings[0].text }}: {{ item.settings[0].settings[0].value }}</span><span v-if="item.settings[0] && item.settings[0].settings[1]">, {{ item.settings[0].settings[1].text }}: {{ item.settings[0].settings[1].value }}</span><span v-if="item.settings[0] && item.settings[0].settings[2] || item.settings[1] && item.settings[1].settings[0]">, ...</span>
</div>
</div>
<div class="bd-settingsarray-item-controls">
<span class="bd-settingsarray-open" v-if="typeof setting.allow_external !== 'undefined' ? setting.allow_external || !setting.inline : true" @click="() => showModal(item, index)"><MiOpenInNew v-if="setting.inline" /><MiSettings v-else /></span>
<span class="bd-settingsarray-remove" :class="{'bd-disabled': setting.disabled || setting.min && items.length <= setting.min}" @click="() => removeItem(item)"><MiMinus /></span>
</div>
</div>
</div>
</div>
</template>
<script>
import { shell } from 'electron';
import { Utils, ClientIPC } from 'common';
import { MiSettings, MiOpenInNew, MiMinus } from '../../common';
import { Modals } from 'ui';
import SettingsPanel from '../SettingsPanel.vue';
export default {
props: ['setting', 'change'],
components: {
MiSettings, MiOpenInNew, MiMinus
},
data() {
return {
items: []
};
},
watch: {
setting(value) {
// this.setting was changed
this.reloadSettings();
}
},
methods: {
addItem(openModal) {
if (this.setting.disabled || this.setting.max && this.items.length >= this.setting.max) return;
const item = { settings: this.getItemSettings({}) };
if (openModal) this.showModal(item, this.items.length);
this.items.push(item);
this.update();
},
removeItem(item) {
if (this.setting.disabled || this.setting.min && this.items.length <= this.setting.min) return;
this.items = this.items.filter(i => i !== item);
this.update();
},
changeInItem(item, category_id, setting_id, value) {
console.log('Setting', item, category_id, setting_id, 'to', value);
const category = item.settings.find(c => c.category === category_id);
if (!category) return;
const setting = category.settings.find(s => s.id === setting_id);
if (!setting || Utils.compare(setting.value, value)) return;
setting.value = value;
setting.changed = !Utils.compare(setting.value, setting.old_value);
this.update();
},
update() {
this.change(this.items.map(item => ({
settings: item.settings ? item.settings.map(category => ({
category: category.category,
settings: category.settings.map(setting => ({
id: setting.id,
value: setting.value
}))
})) : []
})));
},
showModal(item, index) {
Modals.settings(this.setting.headertext ? this.setting.headertext.replace(/%n/, index + 1) : this.setting.text + ` #${index + 1}`, item.settings, this.setting.schemes, () => this.update());
},
getItemSettings(item) {
const settings = JSON.parse(JSON.stringify(this.setting.settings));
const newSettings = item.settings || [];
for (let newCategory of newSettings) {
const category = settings.find(c => c.category === newCategory.category);
for (let newSetting of newCategory.settings) {
const setting = category.settings.find(s => s.id === newSetting.id);
setting.value = setting.old_value = newSetting.value;
setting.changed = false;
}
}
return settings;
},
reloadSettings() {
this.items = JSON.parse(JSON.stringify(this.setting.value)) || [];
this.items = this.items.map(item => ({ settings: this.getItemSettings(item) }));
}
},
beforeCreate() {
// https://vuejs.org/v2/guide/components.html#Circular-References-Between-Components
this.$options.components.SettingsPanel = SettingsPanel;
},
beforeMount() {
this.reloadSettings();
}
}
</script>

View File

@ -11,7 +11,7 @@
<template>
<div class="bd-form-dropdown">
<div class="bd-title">
<h3>{{setting.text}}</h3>
<h3 v-if="setting.text">{{setting.text}}</h3>
<div class="bd-dropdown" :class="{'bd-active': active}">
<div class="bd-dropdown-current" @click="() => active = !active && !setting.disabled">
<span class="bd-dropdown-text">{{getOptionText(setting.value)}}</span>

View File

@ -11,7 +11,7 @@
<template>
<div class="bd-form-numberinput">
<div class="bd-title">
<h3>{{setting.text}}</h3>
<h3 v-if="setting.text">{{setting.text}}</h3>
<div class="bd-number">
<input type="number" :value="setting.value" :min="setting.min" :max="setting.max" :step="setting.step" @keyup.stop @input="input"/>
<div class="bd-number-spinner bd-flex bd-flex-col">

View File

@ -9,7 +9,7 @@
*/
<template>
<div class="bd-form-item" :class="{'bd-form-item-changed': changed, 'bd-disabled': disabled}">
<div class="bd-form-item" :class="{'bd-form-item-changed': changed, 'bd-disabled': disabled, 'bd-form-item-noheader': !setting.text}">
<BoolSetting v-if="setting.type === 'bool'" :setting="setting" :change="change"/>
<DropdownSetting v-if="setting.type === 'dropdown'" :setting="setting" :change="change"/>
<NumberSetting v-if="setting.type === 'number'" :setting="setting" :change="change"/>
@ -18,6 +18,7 @@
<MultilineTextSetting v-if="setting.type === 'text' && setting.multiline" :setting="setting" :change="change"/>
<SliderSetting v-if="setting.type === 'slider'" :setting="setting" :change="change"/>
<FileSetting v-if="setting.type === 'file'" :setting="setting" :change="change"/>
<ArraySetting v-if="setting.type === 'array'" :setting="setting" :change="change" />
<div class="bd-form-divider"></div>
</div>
</template>
@ -31,6 +32,7 @@
import MultilineTextSetting from './Multiline.vue';
import SliderSetting from './Slider.vue';
import FileSetting from './File.vue';
import ArraySetting from './Array.vue';
export default {
props: [
@ -45,7 +47,8 @@
StringSetting,
MultilineTextSetting,
SliderSetting,
FileSetting
FileSetting,
ArraySetting
},
computed: {
changed() {

View File

@ -11,7 +11,7 @@
<template>
<div class="bd-form-textinput">
<div class="bd-title">
<h3>{{setting.text}}</h3>
<h3 v-if="setting.text">{{setting.text}}</h3>
<div class="bd-textinput-wrapper">
<input type="text" :value="setting.value" @keyup.stop @input="input"/>
</div>

View File

@ -1,2 +1,2 @@
export { default as BdSettingsWrapper } from './BdSettingsWrapper.vue';
export { default as BdSettings } from './BdSettings.vue';
export { default as BdSettings } from './BdSettings.vue';

View File

@ -11,7 +11,7 @@
<template>
<div class="bd-sidebar-view" :class="{active: contentVisible, animating: animating}">
<div class="bd-sidebar-region bd-flex-col">
<div class="bd-settingsWrap">
<div class="bd-settingswrap">
<ScrollerWrap dark="true">
<slot name="sidebar" />
</ScrollerWrap>

View File

@ -11,6 +11,7 @@
import { Utils, FileUtils } from 'common';
import { Settings, Events, PluginManager, ThemeManager } from 'modules';
import BasicModal from './components/bd/modals/BasicModal.vue';
import ConfirmModal from './components/bd/modals/ConfirmModal.vue';
import ErrorModal from './components/bd/modals/ErrorModal.vue';
import SettingsModal from './components/bd/modals/SettingsModal.vue';
@ -25,6 +26,7 @@ export default class {
};
modal.closing = false;
modal.close = force => this.close(modal, force);
modal.id = Date.now();
this.stack.push(modal);
Events.emit('bd-refresh-modals');
@ -68,6 +70,17 @@ export default class {
return this.add({ title, text }, BasicModal);
}
static confirm(title, text) {
const modal = { title, text };
const promise = new Promise((resolve, reject) => {
modal.confirm = () => resolve(true);
modal.beforeClose = () => reject();
this.add(modal, ConfirmModal);
});
modal.promise = promise;
return modal;
}
static error(event) {
return this.add({ event }, ErrorModal);
}

View File

@ -59,4 +59,4 @@ module.exports = {
path.resolve(__dirname, '..', 'node_modules')
]
}*/
};
};

View File

@ -14,6 +14,7 @@ const
_ = require('lodash');
import { Vendor } from 'modules';
import filetype from 'file-type';
export class Utils {
static overload(fn, cb) {
@ -150,6 +151,15 @@ export class FileUtils {
});
}
static async readFileBuffer(path, options) {
return new Promise((resolve, reject) => {
fs.readFile(path, options || {}, (err, data) => {
if (err) return reject(err);
resolve(data);
});
});
}
static async writeFile(path, data) {
return new Promise((resolve, reject) => {
fs.writeFile(path, data, err => {
@ -196,4 +206,16 @@ export class FileUtils {
static async readDir(path) {
return this.listDirectory(path);
}
static async getFileType(buffer) {
if (typeof buffer === 'string') buffer = await this.readFileBuffer(buffer);
return filetype(buffer);
}
static async toDataURI(buffer, type) {
if (typeof buffer === 'string') buffer = await this.readFileBuffer(buffer);
if (!type) type = this.getFileType(buffer).mime;
return `data:${type};base64,${buffer.toString('base64')}`;
}
}

View File

@ -27,6 +27,7 @@
"electron-rebuild": "^1.7.3",
"eslint": "^4.16.0",
"eslint-plugin-vue": "^4.2.0",
"file-type": "^7.6.0",
"gulp": "^3.9.1",
"gulp-babel": "^7.0.0",
"gulp-plumber": "^1.2.0",
@ -49,7 +50,7 @@
"build": "npm run build --prefix client && npm run build --prefix core && npm run build --prefix csseditor",
"build_client": "npm run build --prefix client",
"watch_client": "npm run watch --prefix client",
"build_core": "npm run build --prefix core",
"build_core": "npm run build --prefix core",
"watch_core": "npm run watch --prefix core",
"watch_csseditor": "npm run watch --prefix csseditor",
"lint": "eslint -f unix client/src core/src csseditor/src",

View File

@ -17,6 +17,77 @@
"category_default_comment": "default category has no header and is always displayed first",
"category": "default",
"settings": [
{
"id": "array-1",
"type": "array",
"value": null,
"text": "Test settings array",
"hint": "Just a test. Inline should be left as false here in most cases. (Only set it to true if there's only one setting otherwise it takes up too much space. Or you could put it in a drawer.)",
"inline": false,
"min": 1,
"max": 5,
"settings": [
{
"category_default_comment": "default category has no header and is always displayed first",
"category": "default",
"settings": [
{
"id": "default-0",
"type": "bool",
"value": false,
"text": "Bool Test Setting 3",
"hint": "Bool Test Setting Hint 3"
},
{
"id": "default-1",
"type": "text",
"value": "defaultValue",
"text": "Text Test Setting",
"hint": "Text Test Setting Hint"
}
]
}
],
"schemes": [
{
"id": "scheme-1",
"name": "Test scheme",
"hint": "Can even use schemes here.",
"icon_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Cow_female_black_white.jpg/220px-Cow_female_black_white.jpg",
"settings": [
{
"category": "default",
"settings": [
{
"id": "default-0",
"value": true
}
]
}
]
},
{
"id": "scheme-2",
"name": "Another test scheme",
"icon_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Cow_female_black_white.jpg/220px-Cow_female_black_white.jpg",
"settings": [
{
"category": "default",
"settings": [
{
"id": "default-0",
"value": false
},
{
"id": "default-1",
"value": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Cow_female_black_white.jpg/220px-Cow_female_black_white.jpg"
}
]
}
]
}
]
},
{
"id": "default-0",
"type": "bool",

View File

@ -17,8 +17,106 @@
"type": "text",
"value": "#00ff00",
"text": "Primary colour",
"hint": "A colour setting type would be nice here",
"scss_raw": true
"hint": "A colour setting type would be nice here"
},
{
"id": "additional-colours",
"type": "array",
"text": "Additional colours",
"inline": true,
"allow_external": false,
"value": [
{
"settings": [
{
"category": "default",
"settings": [
{
"id": "colour",
"value": "#ff0000"
}
]
}
]
},
{
"settings": [
{
"category": "default",
"settings": [
{
"id": "colour",
"value": "#ffff00"
}
]
}
]
},
{
"settings": [
{
"category": "default",
"settings": [
{
"id": "colour",
"value": "#ffffff"
}
]
}
]
},
{
"settings": [
{
"category": "default",
"settings": [
{
"id": "colour",
"value": "#00ffff"
}
]
}
]
},
{
"settings": [
{
"category": "default",
"settings": [
{
"id": "colour",
"value": "#0000ff"
}
]
}
]
},
{
"settings": [
{
"category": "default",
"settings": [
{
"id": "colour",
"value": "#000000"
}
]
}
]
}
],
"settings": [
{
"category": "default",
"settings": [
{
"id": "colour",
"type": "text",
"value": "#ff0000"
}
]
}
]
},
{
"id": "spanOpacity",
@ -34,8 +132,7 @@
},
{
"id": "avatar",
"type": "text",
"value": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Cow_female_black_white.jpg/220px-Cow_female_black_white.jpg",
"type": "file",
"text": "Avatar replace",
"hint": "Replace all avatars"
},

View File

@ -1,7 +1,7 @@
@import 'vars.scss';
@import 'vars';
div {
background: $divBg;
background: unquote($divBg);
}
span {
@ -11,6 +11,18 @@ span {
}
.avatar-large {
background-image: url($avatar) !important;
background-image: url(map-get($avatar, url)) !important;
border-radius: $avatarRadius !important;
}
// Can't use a for loop as then we don't get the index
$index: 0;
@while $index < length($additional-colours) {
$additional-colour: nth($additional-colours, $index + 1);
div:nth-child(#{length($additional-colours)}n + #{$index}) {
color: unquote(map-get($additional-colour, colour)) !important;
}
$index: $index + 1;
}

View File

@ -1,6 +1,6 @@
$divBg: green !default;
$spanOpacity: 0.5 !default;
$avatar: "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Cow_female_black_white.jpg/220px-Cow_female_black_white.jpg" !default;
$avatar: (url: "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/Cow_female_black_white.jpg/220px-Cow_female_black_white.jpg") !default;
$avatarRadius: 8px !default;
$radioTest: red !default;
$spanOpacity2: 1 !default;
$spanOpacity2: 1 !default;