Merge pull request #127 from samuelthomas2774/modals

Add modal manager
This commit is contained in:
Alexei Stukov 2018-02-14 00:03:27 +02:00 committed by GitHub
commit 91a658db44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 559 additions and 362 deletions

View File

@ -8,7 +8,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import { DOM, BdUI } from 'ui'; import { DOM, BdUI, Modals } from 'ui';
import BdCss from './styles/index.scss'; import BdCss from './styles/index.scss';
import { Events, CssEditor, Globals, PluginManager, ThemeManager, ModuleManager, WebpackModules, Settings } from 'modules'; import { Events, CssEditor, Globals, PluginManager, ThemeManager, ModuleManager, WebpackModules, Settings } from 'modules';
import { ClientLogger as Logger, ClientIPC } from 'common'; import { ClientLogger as Logger, ClientIPC } from 'common';
@ -22,6 +22,7 @@ class BetterDiscord {
window.events = Events; window.events = Events;
window.wpm = WebpackModules; window.wpm = WebpackModules;
window.bdsettings = Settings; window.bdsettings = Settings;
window.bdmodals = Modals;
DOM.injectStyle(BdCss, 'bdmain'); DOM.injectStyle(BdCss, 'bdmain');
Events.on('global-ready', this.globalReady.bind(this)); Events.on('global-ready', this.globalReady.bind(this));
} }
@ -29,8 +30,9 @@ class BetterDiscord {
async init() { async init() {
await Settings.loadSettings(); await Settings.loadSettings();
await ModuleManager.initModules(); await ModuleManager.initModules();
await PluginManager.loadAllPlugins(); await PluginManager.loadAllPlugins(true);
await ThemeManager.loadAllThemes(); await ThemeManager.loadAllThemes(true);
Modals.showContentManagerErrors();
Events.emit('ready'); Events.emit('ready');
Events.emit('discord-ready'); Events.emit('discord-ready');
} }

View File

@ -13,6 +13,7 @@ import { FileUtils, ClientLogger as Logger } from 'common';
import path from 'path'; import path from 'path';
import { Events } from 'modules'; import { Events } from 'modules';
import { Error } from 'structs'; import { Error } from 'structs';
import { Modals } from 'ui';
export default class { export default class {
@ -28,7 +29,7 @@ export default class {
return this._contentPath ? this._contentPath : (this._contentPath = Globals.getObject('paths').find(path => path.id === this.pathId).path); return this._contentPath ? this._contentPath : (this._contentPath = Globals.getObject('paths').find(path => path.id === this.pathId).path);
} }
static async loadAllContent() { static async loadAllContent(supressErrors) {
try { try {
await FileUtils.ensureDirectory(this.contentPath); await FileUtils.ensureDirectory(this.contentPath);
const directories = await FileUtils.listDirectory(this.contentPath); const directories = await FileUtils.listDirectory(this.contentPath);
@ -47,15 +48,15 @@ export default class {
} }
} }
if (this.errors.length) { if (this.errors.length && !supressErrors) {
Events.emit('bd-error', { Modals.error({
header: `${this.moduleName} - one or more ${this.contentType}(s) failed to load`, header: `${this.moduleName} - ${this.errors.length} ${this.contentType}${this.errors.length !== 1 ? 's' : ''} failed to load`,
module: this.moduleName, module: this.moduleName,
type: 'err', type: 'err',
content: this.errors content: this.errors
}); });
this._errors = [];
} }
this._errors = [];
return this.localContent; return this.localContent;
} catch (err) { } catch (err) {

View File

@ -9,6 +9,7 @@
*/ */
import { FileUtils } from 'common'; import { FileUtils } from 'common';
import { Modals } from 'ui';
export default class { export default class {
@ -48,6 +49,10 @@ export default class {
} }
} }
showSettingsModal() {
return Modals.pluginSettings(this);
}
async saveSettings(newSettings) { async saveSettings(newSettings) {
for (let category of newSettings) { for (let category of newSettings) {
const oldCategory = this.pluginConfig.find(c => c.category === category.category); const oldCategory = this.pluginConfig.find(c => c.category === category.category);

View File

@ -52,15 +52,15 @@ export default class extends ContentManager {
} }
static get moduleName() { static get moduleName() {
return 'PluginManager'; return 'Plugin Manager';
} }
static get pathId() { static get pathId() {
return 'plugins'; return 'plugins';
} }
static async loadAllPlugins() { static async loadAllPlugins(supressErrors) {
const loadAll = await this.loadAllContent(); const loadAll = await this.loadAllContent(supressErrors);
this.localPlugins.forEach(plugin => { this.localPlugins.forEach(plugin => {
if (plugin.type === 'module') return; if (plugin.type === 'module') return;
if (plugin.enabled) plugin.start(); if (plugin.enabled) plugin.start();

View File

@ -9,7 +9,7 @@
*/ */
import ContentManager from './contentmanager'; import ContentManager from './contentmanager';
import { DOM } from 'ui'; import { DOM, Modals } from 'ui';
import { FileUtils, ClientIPC } from 'common'; import { FileUtils, ClientIPC } from 'common';
class Theme { class Theme {
@ -39,6 +39,10 @@ class Theme {
get css() { return this.userConfig.css } get css() { return this.userConfig.css }
get id() { return this.name.toLowerCase().replace(/\s+/g, '-') } get id() { return this.name.toLowerCase().replace(/\s+/g, '-') }
showSettingsModal() {
return Modals.themeSettings(this);
}
async saveSettings(newSettings) { async saveSettings(newSettings) {
for (let category of newSettings) { for (let category of newSettings) {
const oldCategory = this.themeConfig.find(c => c.category === category.category); const oldCategory = this.themeConfig.find(c => c.category === category.category);
@ -132,6 +136,14 @@ export default class ThemeManager extends ContentManager {
return this.localContent; return this.localContent;
} }
static get contentType() {
return 'theme';
}
static get moduleName() {
return 'Theme Manager';
}
static get pathId() { static get pathId() {
return 'themes'; return 'themes';
} }
@ -195,11 +207,11 @@ export default class ThemeManager extends ContentManager {
} }
if (typeof value === 'boolean' || typeof value === 'number') { if (typeof value === 'boolean' || typeof value === 'number') {
return `$${name}: ${value};`; return `$${name}: ${value};`;
} }
if (typeof value === 'string') { if (typeof value === 'string') {
return `$${name}: ${setting.scss_raw ? value : `'${setting.value.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}'`};`; return `$${name}: ${setting.scss_raw ? value : `'${setting.value.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}'`};`;
} }
} }

View File

@ -24,7 +24,7 @@
background-position: center; background-position: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 3000; z-index: 3001;
cursor: pointer; cursor: pointer;
filter: grayscale(100%); filter: grayscale(100%);
opacity: 0.5; opacity: 0.5;
@ -44,7 +44,7 @@
background: transparent; background: transparent;
opacity: 1; opacity: 1;
box-shadow: none; box-shadow: none;
z-index: 90000; z-index: 3001;
.bd-settings-button-btn { .bd-settings-button-btn {
background-image: $logoBigBw; background-image: $logoBigBw;

View File

@ -11,6 +11,7 @@
.bd-plugin-settings-body { .bd-plugin-settings-body {
padding: 0 15px; padding: 0 15px;
margin: 0 0 74px;
.bd-switch-wrapper { .bd-switch-wrapper {
width: 40px; width: 40px;
@ -22,4 +23,10 @@
} }
} }
} }
&.bd-edited {
.bd-scroller::-webkit-scrollbar-track {
margin-bottom: 74px;
}
}
} }

View File

@ -1,9 +1,9 @@
bdtooltips { bd-tooltips {
left: 0; left: 0;
position: absolute; position: absolute;
right: 0; right: 0;
top: 0; top: 0;
z-index: 9000; z-index: 9001;
} }
.bd-tooltip { .bd-tooltip {

View File

@ -4,5 +4,4 @@
@import './forms.scss'; @import './forms.scss';
@import './forms/index.scss'; @import './forms/index.scss';
@import './material-buttons.scss'; @import './material-buttons.scss';
@import './modals.scss';
@import './drawers.scss'; @import './drawers.scss';

View File

@ -1,265 +0,0 @@
.bd-backdrop {
position: fixed;
right: 0px;
left: 0px;
top: 0px;
bottom: 0px;
background: #000;
opacity: .85;
padding: 20px;
z-index: 9000;
justify-content: center;
animation: bd-backdrop-in 0.22s ease;
&.bd-backdrop-out {
animation: bd-backdrop-out 0.22s ease;
}
}
.bd-modal {
position: fixed;
align-content: space-around;
display: flex;
border-radius: 8px;
width: 100%;
height: 100%;
left: 0;
top: 0;
user-select: none;
padding: 60px;
transform: scale(1) translateZ(0px);
-webkit-box-direction: normal;
-webkit-box-orient: vertical;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
align-items: center;
box-sizing: border-box;
pointer-events: none;
z-index: 9001;
justify-content: center;
animation: bd-modal-in 0.22s ease;
&.bd-modal-out {
animation: bd-modal-out 0.22s ease;
}
.bd-modal-header .bd-modal-icon .bd-material-design-icon {
margin-right: 5px;
}
&.bd-err {
.bd-modal-header .bd-modal-icon svg {
fill: $colerr;
}
}
.bd-modal-body .bd-scroller-wrap .bd-scroller {
color: #FFF;
}
.bd-modal-controls {
display: flex;
padding: 15px;
border-top: 1px solid #4a4a4a;
.bd-modal-tip {
flex-grow: 1;
line-height: 26px;
color: #FFF;
}
.bd-button {
padding: 5px 10px;
border-radius: 3px;
}
}
}
.bd-modal .bd-modal-inner {
background: #36393e;
contain: layout;
flex-direction: column;
pointer-events: auto;
-webkit-box-direction: normal;
-webkit-box-orient: vertical;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
flex-grow: 1;
border-radius: 4px;
max-height: 100%;
max-width: 500px;
width: 500px;
}
.bd-modal .bd-modal-body {
display: flex;
}
.bd-modal {
.bd-modal-header {
display: flex;
padding: 15px;
flex: 0 0;
-webkit-transition: -webkit-box-shadow .1s ease-out;
transition: -webkit-box-shadow .1s ease-out;
transition: box-shadow .1s ease-out;
.bd-modal-headertext {
color: #FFF;
font-weight: 700;
flex-grow: 1;
line-height: 18px;
padding: 1px;
}
.bd-modal-x {
display: flex;
width: 20px;
height: 20px;
border-radius: 3px;
cursor: pointer;
align-content: center;
justify-content: center;
align-items: center;
margin-left: -2px -2px -2px 10px;
padding: 2px;
.bd-material-design-icon {
fill: #ccc;
}
&:hover {
background: #2d2f34;
.bd-material-design-icon {
fill: #fff;
}
}
}
}
&.bd-modal-scrolled .bd-modal-header {
-webkit-box-shadow: 0 1px 0 0 rgba(24,25,28,.3), 0 1px 2px 0 rgba(24,25,28,.3);
box-shadow: 0 1px 0 0 rgba(24,25,28,.3), 0 1px 2px 0 rgba(24,25,28,.3);
}
.bd-modal-body {
padding: 0 15px;
}
.bd-modal-footer {
.bd-footer-alert {
margin: 10px;
height: 0;
display: flex;
background-color: rgba(32, 34, 37, 0.9);
box-shadow: 0 2px 10px 0 rgba(0,0,0,.2);
padding: 10px 10px 10px 16px;
overflow: hidden;
border-radius: 5px;
transform: translateY(100%);
opacity: 0;
transition: all .2s ease-in-out;
&.bd-warn {
background-color: $colerr;
animation: bd-warn-shake 0.4s;
}
&.bd-active {
height: auto;
opacity: 1;
transform: none;
}
.bd-footer-alert-text {
flex: 1 1 auto;
color: #FFF;
font-weight: 700;
line-height: 34px;
}
.bd-button {
height: 30px;
border-radius: 3px;
padding: 2px 16px;
&.bd-tp {
background: transparent;
&:hover {
text-decoration: underline;
background: transparent;
}
}
}
}
}
}
.bd-modal-error .bd-modal-error-title {
padding: 5px;
background: rgba(0,0,0,.3);
border-radius: 3px 3px 0 0;
}
.bd-modal-error {
margin-top: 5px;
.bd-scroller-wrap {
background: rgba(0,0,0,.2);
.bd-scroller {
overflow-x: auto;
padding: 0;
&::-webkit-scrollbar-corner {
background: transparent;
}
}
}
}
.bd-modal-error .bd-scroller-wrap {
opacity: 0;
}
.bd-modal-error.bd-open {
.bd-modal-error-body {
transform: scaleY(1) translateY(0%);
margin-top: 0%;
opacity: 1;
user-select: all;
span {
font-weight: 700;
}
}
.bd-scroller-wrap {
opacity: 1;
}
}
.bd-modal-error .bd-modal-error-body {
white-space: pre-wrap;
font-size: 12px;
font-family: 'Consolas';
padding: 0 5px;
border-radius: 3px;
max-height: 100px;
width: auto;
transition: transform 0.2s ease, margin-top 0.2s ease, opacity 0.2s ease;
transform: scaleY(0) translateY(0%);
margin-top: -50%;
opacity: 0;
}
.bd-modal-titlelink {
cursor: pointer;
color: $colbdblue;
&:hover {
text-decoration: underline;
color: lighten($colbdblue, 5%);
}
}

View File

@ -6,6 +6,7 @@
@import './sidebarview/index.scss'; @import './sidebarview/index.scss';
@import './bdsettings/index.scss'; @import './bdsettings/index.scss';
@import './generic/index.scss'; @import './generic/index.scss';
@import './modals/index.scss';
@import './profilebadges.scss'; @import './profilebadges.scss';
@import './discordoverrides.scss'; @import './discordoverrides.scss';

View File

@ -0,0 +1,17 @@
.bd-backdrop {
position: fixed;
right: 0px;
left: 0px;
top: 0px;
bottom: 0px;
background: #000;
opacity: .85;
padding: 20px;
z-index: 9000;
justify-content: center;
animation: bd-backdrop-in 0.22s ease;
&.bd-backdrop-out {
animation: bd-backdrop-out 0.22s ease;
}
}

View File

@ -0,0 +1,5 @@
.bd-modal-basic {
.bd-modal-basic-body {
padding-bottom: 15px;
}
}

View File

@ -0,0 +1,66 @@
.bd-modal.bd-err {
.bd-modal-header .bd-modal-icon svg {
fill: $colerr;
}
.bd-modal-body {
padding-bottom: 15px;
min-height: 70px;
}
}
.bd-modal-error .bd-modal-error-title {
padding: 5px;
background: rgba(0,0,0,.3);
border-radius: 3px 3px 0 0;
}
.bd-modal-error {
margin-top: 5px;
.bd-scroller-wrap {
background: rgba(0,0,0,.2);
.bd-scroller {
overflow-x: auto;
padding: 0;
&::-webkit-scrollbar-corner {
background: transparent;
}
}
}
}
.bd-modal-error .bd-scroller-wrap {
opacity: 0;
}
.bd-modal-error.bd-open {
.bd-modal-error-body {
transform: scaleY(1) translateY(0%);
margin-top: 0%;
opacity: 1;
user-select: all;
span {
font-weight: 700;
}
}
.bd-scroller-wrap {
opacity: 1;
}
}
.bd-modal-error .bd-modal-error-body {
white-space: pre-wrap;
font-size: 12px;
font-family: 'Consolas';
padding: 0 5px;
border-radius: 3px;
max-height: 100px;
width: auto;
transition: transform 0.2s ease, margin-top 0.2s ease, opacity 0.2s ease;
transform: scaleY(0) translateY(0%);
margin-top: -50%;
opacity: 0;
}

View File

@ -0,0 +1,49 @@
.bd-modal-footer {
.bd-footer-alert {
margin: -64px 10px 10px;
height: 0;
display: flex;
background-color: rgba(32, 34, 37, 0.9);
box-shadow: 0 2px 10px 0 rgba(0,0,0,.2);
padding: 10px 10px 10px 16px;
overflow: hidden;
border-radius: 5px;
transform: translateY(100%);
opacity: 0;
transition: all .2s ease-in-out;
position: relative;
&.bd-warn {
background-color: $colerr;
animation: bd-warn-shake 0.4s;
}
&.bd-active {
height: auto;
opacity: 1;
transform: none;
}
.bd-footer-alert-text {
flex: 1 1 auto;
color: #fff;
font-weight: 700;
line-height: 34px;
}
.bd-button {
height: 30px;
border-radius: 3px;
padding: 2px 16px;
&.bd-tp {
background: transparent;
&:hover {
text-decoration: underline;
background: transparent;
}
}
}
}
}

View File

@ -0,0 +1,50 @@
.bd-modal-header {
display: flex;
padding: 15px;
flex: 0 0;
-webkit-transition: -webkit-box-shadow .1s ease-out;
transition: -webkit-box-shadow .1s ease-out;
transition: box-shadow .1s ease-out;
.bd-modal-headertext {
color: #fff;
font-weight: 700;
flex-grow: 1;
line-height: 18px;
padding: 1px;
}
.bd-modal-icon .bd-material-design-icon {
margin-right: 5px;
}
.bd-modal-x {
display: flex;
width: 20px;
height: 20px;
border-radius: 3px;
cursor: pointer;
align-content: center;
justify-content: center;
align-items: center;
margin-left: -2px -2px -2px 10px;
padding: 2px;
.bd-material-design-icon {
fill: #ccc;
}
&:hover {
background: #2d2f34;
.bd-material-design-icon {
fill: #fff;
}
}
}
}
.bd-modal-scrolled .bd-modal-header {
-webkit-box-shadow: 0 1px 0 0 rgba(24,25,28,.3), 0 1px 2px 0 rgba(24,25,28,.3);
box-shadow: 0 1px 0 0 rgba(24,25,28,.3), 0 1px 2px 0 rgba(24,25,28,.3);
}

View File

@ -0,0 +1,7 @@
@import './backdrop.scss';
@import './modals.scss';
@import './header.scss';
@import './footer-alert.scss';
@import './basic-modal.scss';
@import './error-modal.scss';

View File

@ -0,0 +1,96 @@
.bd-modal-wrap {
transition: all 0.2s ease;
width: 100%;
height: 100%;
position: absolute;
z-index: 9000;
.bd-modal-close-area {
width: 100%;
height: 100%;
}
}
.bd-modal {
position: fixed;
align-content: space-around;
display: flex;
border-radius: 8px;
width: 100%;
height: 100%;
left: 0;
top: 0;
user-select: none;
padding: 60px;
transform: scale(1) translateZ(0px);
-webkit-box-direction: normal;
-webkit-box-orient: vertical;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
align-items: center;
box-sizing: border-box;
pointer-events: none;
z-index: 9001;
justify-content: center;
animation: bd-modal-in 0.22s ease;
&.bd-modal-out {
animation: bd-modal-out 0.22s ease;
}
.bd-modal-inner {
background: #36393e;
contain: layout;
flex-direction: column;
pointer-events: auto;
-webkit-box-direction: normal;
-webkit-box-orient: vertical;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
flex-grow: 1;
border-radius: 4px;
max-height: 100%;
max-width: 500px;
width: 500px;
}
.bd-modal-body {
padding: 0 15px;
display: flex;
.bd-scroller-wrap .bd-scroller {
color: #fff;
margin: 0;
padding: 0;
}
}
.bd-modal-titlelink {
cursor: pointer;
color: $colbdblue;
&:hover {
text-decoration: underline;
color: lighten($colbdblue, 5%);
}
}
.bd-modal-controls {
display: flex;
padding: 15px;
border-top: 1px solid #4a4a4a;
.bd-modal-tip {
flex-grow: 1;
line-height: 26px;
color: #FFF;
}
.bd-button {
padding: 5px 10px;
border-radius: 3px;
}
}
}

View File

@ -35,9 +35,9 @@ export default class {
} }
static injectUi() { static injectUi() {
DOM.createElement('bdtooltips').appendTo(DOM.bdBody);
DOM.createElement('div', null, 'bd-settings').appendTo(DOM.bdBody); DOM.createElement('div', null, 'bd-settings').appendTo(DOM.bdBody);
DOM.createElement('div', null, 'bd-modals').appendTo(DOM.bdModals); DOM.createElement('div', null, 'bd-modals').appendTo(DOM.bdModals);
DOM.createElement('bd-tooltips').appendTo(DOM.bdBody);
const modals = new Vue({ const modals = new Vue({
el: '#bd-modals', el: '#bd-modals',

View File

@ -12,9 +12,6 @@
<div class="bd-settings" :class="{active: active}" @keyup="close"> <div class="bd-settings" :class="{active: active}" @keyup="close">
<SidebarView :contentVisible="this.activeIndex >= 0" :animating="this.animating" :class="{'bd-stop': !first}"> <SidebarView :contentVisible="this.activeIndex >= 0" :animating="this.animating" :class="{'bd-stop': !first}">
<Sidebar slot="sidebar"> <Sidebar slot="sidebar">
<div class="bd-settings-button bd-active">
<div class="bd-settings-button-btn"></div>
</div>
<div class="bd-settings-x" @click="close"> <div class="bd-settings-x" @click="close">
<MiClose size="17"/> <MiClose size="17"/>
<span class="bd-x-text">ESC</span> <span class="bd-x-text">ESC</span>

View File

@ -10,7 +10,7 @@
<template> <template>
<div class="bd-settings-wrapper" :class="[{active: active}, 'platform-' + this.platform]"> <div class="bd-settings-wrapper" :class="[{active: active}, 'platform-' + this.platform]">
<div class="bd-settings-button" @click="showSettings"> <div class="bd-settings-button" :class="{'bd-active': active}" @click="showSettings">
<div class="bd-settings-button-btn" :class="[{'bd-loading': !loaded}]"></div> <div class="bd-settings-button-btn" :class="[{'bd-loading': !loaded}]"></div>
</div> </div>
<BdSettings :active="active" :close="hideSettings" /> <BdSettings :active="active" :close="hideSettings" />

View File

@ -8,66 +8,50 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
<template> <template>
<div class="bd-modals-container"> <div class="bd-modals-container">
<div v-for="(modal, index) in modals" :key="`bd-modal-${index}`"> <div v-for="(modal, index) in modals.stack" :key="`bd-modal-${index}`">
<div v-if="index === 0" class="bd-backdrop" @click="closeModal(index)"></div> <div class="bd-backdrop" :class="{'bd-backdrop-out': modal.closing}" :style="{opacity: index === 0 ? undefined : 0}"></div>
<div v-else :style="{opacity: 0}" class="bd-backdrop" @click="closeModal(index)"></div> <div class="bd-modal-wrap" :style="{transform: `scale(${downscale(index + 1, 0.2)})`, opacity: downscale(index + 1, 1)}">
<Modal :headerText="modal.header" <div class="bd-modal-close-area" @click="closeModal(modal)"></div>
:close="() => closeModal(index)" <component :is="modal.component" />
:class="[{'bd-err': modal.type && modal.type === 'err'}, {'bd-modal-out': modal.closing}]"> </div>
<MiError v-if="modal.type === 'err'" slot="icon" size="20"/>
<div slot="body">
<div v-for="(content, index) in modal.content">
<ErrorModal v-if="content._type === 'err'" :content="content" :hideStack="hideStack" :showStack="showStack"/>
</div>
</div>
<div slot="footer" class="bd-modal-controls">
<span class="bd-modal-tip">Ctrl+Shift+I for more details</span>
<div class="bd-button bd-ok" @click="closeModal(index)">
OK
</div>
</div>
</Modal>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
// Imports // Imports
import { Events } from 'modules'; import { Events } from 'modules';
import { Modals } from 'ui';
import { Modal } from '../common'; import { Modal } from '../common';
import { MiError } from '../common/MaterialIcon'; import { MiError } from '../common/MaterialIcon';
import ErrorModal from '../common/ErrorModal.vue'; import ErrorModal from './modals/ErrorModal.vue';
export default { export default {
components: {
Modal, MiError
},
data() { data() {
return { return {
modals: [] modals: Modals,
} eventListener: null
};
}, },
components: { created() {
Modal, MiError, ErrorModal console.log(this);
}, Events.on('bd-refresh-modals', this.eventListener = () => {
beforeMount() { this.$forceUpdate();
Events.on('bd-error', e => {
e.closing = false;
this.modals.push(e);
console.log(this.modals);
}); });
}, },
destroyed() {
if (this.eventListener) Events.off('bd-refresh-modals', this.eventListener);
},
methods: { methods: {
closeModal(index) { closeModal(modal) {
this.modals[index].closing = true; modal.close();
setTimeout(() => {
this.modals.splice(index, 1);
}, 200);
}, },
showStack(error) { downscale(index, times) {
error.showStack = true; return 1 - ((this.modals.stack.filter(m => !m.closing).length - index) * times);
},
hideStack(error) {
error.showStack = false;
} }
} }
} }

View File

@ -32,28 +32,26 @@
<div class="bd-spinner-2"></div> <div class="bd-spinner-2"></div>
</div> </div>
</div> </div>
<PluginSettingsModal v-if="settingsOpen !== null" :plugin="settingsOpen" :close="closeSettings" />
</SettingsWrapper> </SettingsWrapper>
</template> </template>
<script> <script>
// Imports // Imports
import { PluginManager } from 'modules'; import { PluginManager } from 'modules';
import { Modals } from 'ui';
import { SettingsWrapper } from './'; import { SettingsWrapper } from './';
import PluginCard from './PluginCard.vue'; import PluginCard from './PluginCard.vue';
import PluginSettingsModal from './PluginSettingsModal.vue';
import { MiRefresh } from '../common'; import { MiRefresh } from '../common';
export default { export default {
data() { data() {
return { return {
local: true, local: true,
settingsOpen: null,
localPlugins: PluginManager.localPlugins localPlugins: PluginManager.localPlugins
} }
}, },
components: { components: {
SettingsWrapper, PluginCard, PluginSettingsModal, SettingsWrapper, PluginCard,
MiRefresh MiRefresh
}, },
methods: { methods: {
@ -91,12 +89,8 @@
})(); })();
}, },
showSettings(plugin) { showSettings(plugin) {
this.settingsOpen = plugin; return Modals.pluginSettings(plugin);
},
closeSettings() {
this.settingsOpen = null;
} }
} }
} }
</script> </script>

View File

@ -32,29 +32,26 @@
<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>
<script> <script>
// Imports // Imports
import { ThemeManager } from 'modules'; import { ThemeManager } from 'modules';
import { Modals } from 'ui';
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 {
data() { data() {
return { return {
local: true, local: true,
settingsOpen: null,
localThemes: ThemeManager.localThemes localThemes: ThemeManager.localThemes
} }
}, },
components: { components: {
SettingsWrapper, ThemeCard, SettingsWrapper, ThemeCard,
ThemeSettingsModal,
MiRefresh MiRefresh
}, },
methods: { methods: {
@ -92,12 +89,8 @@
})(); })();
}, },
showSettings(theme) { showSettings(theme) {
this.settingsOpen = theme; return Modals.themeSettings(theme);
},
closeSettings() {
this.settingsOpen = null;
} }
} }
} }
</script> </script>

View File

@ -0,0 +1,31 @@
/**
* BetterDiscord Basic 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 bd-ok" @click="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,47 @@
<template>
<Modal :headerText="modal.event.header" :close="modal.close"
:class="[{'bd-err': modal.event.type && modal.event.type === 'err'}, {'bd-modal-out': modal.closing}]">
<MiError v-if="modal.event.type === 'err'" slot="icon" size="20"/>
<div slot="body">
<div v-for="(content, index) in modal.event.content">
<div v-if="content._type === 'err'" class="bd-modal-error" :class="{'bd-open': content.showStack}">
<div class="bd-modal-error-title bd-flex">
<span class="bd-modal-title-text bd-flex-grow">{{content.message}}</span>
<span class="bd-modal-titlelink" v-if="content.showStack" @click="() => { content.showStack = false; $forceUpdate(); }">Hide Stacktrace</span>
<span class="bd-modal-titlelink" v-else @click="() => { content.showStack = true; $forceUpdate(); }">Show Stacktrace</span>
</div>
<div class="bd-scroller-wrap">
<div class="bd-scroller">
<div class="bd-modal-error-body"><span>{{content.err.message}}</span>
{{content.stackTrace}}</div>
</div>
</div>
</div>
</div>
</div>
<div slot="footer" class="bd-modal-controls">
<span class="bd-modal-tip">Press {{ platformInspectorKey }} for more details</span>
<div class="bd-button bd-ok" @click="modal.close">OK</div>
</div>
</Modal>
</template>
<script>
// Imports
import { Modal } from '../../common';
import { MiError } from '../../common/MaterialIcon';
const process = window.require('process');
export default {
props: ['modal'],
components: {
Modal, MiError
},
computed: {
platformInspectorKey() {
return process.platform === 'darwin' ? 'Cmd+Option+I' : 'Ctrl+Shift+I';
}
}
}
</script>

View File

@ -10,8 +10,7 @@
<template> <template>
<div class="bd-plugin-settings-modal" :class="{'bd-edited': changed}"> <div class="bd-plugin-settings-modal" :class="{'bd-edited': changed}">
<div class="bd-backdrop" @click="attemptToClose" :class="{'bd-backdrop-out': closing}"></div> <Modal :headerText="plugin.name + ' Settings'" :close="attemptToClose" :class="{'bd-modal-out': modal.closing}">
<Modal :headerText="plugin.name + ' Settings'" :close="attemptToClose" :class="{'bd-modal-out': closing}">
<SettingsPanel :settings="configCache" :change="settingChange" slot="body" class="bd-plugin-settings-body" /> <SettingsPanel :settings="configCache" :change="settingChange" slot="body" class="bd-plugin-settings-body" />
<div slot="footer" class="bd-footer-alert" :class ="{'bd-active': changed, 'bd-warn': warnclose}"> <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-footer-alert-text">Unsaved changes</div>
@ -27,11 +26,11 @@
<script> <script>
// Imports // Imports
import Vue from 'vue'; import Vue from 'vue';
import { Modal } from '../common'; import { Modal } from '../../common';
import SettingsPanel from './SettingsPanel.vue'; import SettingsPanel from '../SettingsPanel.vue';
export default { export default {
props: ['plugin','close'], props: ['modal'],
data() { data() {
return { return {
changed: false, changed: false,
@ -43,7 +42,10 @@
}, },
components: { components: {
Modal, Modal,
SettingsPanel, SettingsPanel
},
computed: {
plugin() { return this.modal.plugin; }
}, },
methods: { methods: {
checkForChanges() { checkForChanges() {
@ -96,7 +98,7 @@
if (!this.changed) { if (!this.changed) {
this.closing = true; this.closing = true;
setTimeout(() => { setTimeout(() => {
this.close(); this.modal.close();
}, 200); }, 200);
return; return;
} }

View File

@ -10,8 +10,7 @@
<template> <template>
<div class="bd-plugin-settings-modal" :class="{'bd-edited': changed}"> <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': modal.closing}">
<Modal :headerText="theme.name + ' Settings'" :close="attemptToClose" :class="{'bd-modal-out': closing}">
<SettingsPanel :settings="configCache" :change="settingChange" slot="body" class="bd-plugin-settings-body" /> <SettingsPanel :settings="configCache" :change="settingChange" slot="body" class="bd-plugin-settings-body" />
<div slot="footer" class="bd-footer-alert" :class ="{'bd-active': changed, 'bd-warn': warnclose}"> <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-footer-alert-text">Unsaved changes</div>
@ -27,11 +26,11 @@
<script> <script>
// Imports // Imports
import Vue from 'vue'; import Vue from 'vue';
import { Modal } from '../common'; import { Modal } from '../../common';
import SettingsPanel from './SettingsPanel.vue'; import SettingsPanel from '../SettingsPanel.vue';
export default { export default {
props: ['theme', 'close'], props: ['modal'],
data() { data() {
return { return {
changed: false, changed: false,
@ -45,6 +44,9 @@
Modal, Modal,
SettingsPanel SettingsPanel
}, },
computed: {
theme() { return this.modal.theme; }
},
methods: { methods: {
checkForChanges() { checkForChanges() {
let changed = false; let changed = false;
@ -96,7 +98,7 @@
if (!this.changed) { if (!this.changed) {
this.closing = true; this.closing = true;
setTimeout(() => { setTimeout(() => {
this.close(); this.modal.close();
}, 200); }, 200);
return; return;
} }

93
client/src/ui/modals.js Normal file
View File

@ -0,0 +1,93 @@
/**
* BetterDiscord Modals
* 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.
*/
import { FileUtils } from 'common';
import { Events, PluginManager, ThemeManager } from 'modules';
import BasicModal from './components/bd/modals/BasicModal.vue';
import ErrorModal from './components/bd/modals/ErrorModal.vue';
import PluginSettingsModal from './components/bd/modals/PluginSettingsModal.vue';
import ThemeSettingsModal from './components/bd/modals/ThemeSettingsModal.vue';
export default class {
static add(modal, component) {
modal.component = modal.component || {
template: '<custom-modal :modal="modal" />',
components: { 'custom-modal': component },
data() { return { modal }; },
created() { modal.vue = this; }
};
modal.closing = false;
modal.close = () => this.close(modal);
this.stack.push(modal);
Events.emit('bd-refresh-modals');
return modal;
}
static close(modal) {
return new Promise((resolve, reject) => {
modal.closing = true;
setTimeout(() => {
this._stack = this.stack.filter(m => m !== modal);
Events.emit('bd-refresh-modals');
resolve();
}, 200);
});
}
static closeAll() {
for (let modal of this.stack)
modal.close();
}
static closeLast() {
if (!this.stack.length) return;
this.stack[this.stack.length - 1].close();
}
static basic(title, text) {
return this.add({ title, text }, BasicModal);
}
static error(event) {
return this.add({ event }, ErrorModal);
}
static showContentManagerErrors() {
// Get any errors from PluginManager and ThemeManager
this.error({
header:
(PluginManager.errors.length && ThemeManager.errors.length ? '' :
(PluginManager.errors.length ? PluginManager.moduleName : ThemeManager.moduleName) + ' - ') +
(PluginManager.errors.length ? `${PluginManager.errors.length} ${PluginManager.contentType}${PluginManager.errors.length !== 1 ? 's' : ''}` : '') +
(PluginManager.errors.length && ThemeManager.errors.length ? ' and ' : '') +
(ThemeManager.errors.length ? `${ThemeManager.errors.length} ${ThemeManager.contentType}${ThemeManager.errors.length !== 1 ? 's' : ''}` : '') +
' failed to load',
module: (PluginManager.errors.length && ThemeManager.errors.length ? 'Content Manager' :
(PluginManager.errors.length ? PluginManager.moduleName : ThemeManager.moduleName)),
type: 'err',
content: ([]).concat(PluginManager.errors).concat(ThemeManager.errors)
});
}
static pluginSettings(plugin) {
return this.add({ plugin }, PluginSettingsModal);
}
static themeSettings(theme) {
return this.add({ theme }, ThemeSettingsModal);
}
static get stack() {
return this._stack ? this._stack : (this._stack = []);
}
}

View File

@ -1,4 +1,5 @@
export { default as DOM } from './dom'; export { default as DOM } from './dom';
export { default as BdUI } from './bdui'; export { default as BdUI } from './bdui';
export { default as VueInjector } from './vueinjector'; export { default as VueInjector } from './vueinjector';
export { default as Modals } from './modals';
export { default as ProfileBadges } from './profilebadges'; export { default as ProfileBadges } from './profilebadges';

View File

@ -13,7 +13,7 @@ import Vue from 'vue';
import VTooltip from 'v-tooltip'; import VTooltip from 'v-tooltip';
Vue.use(VTooltip, { Vue.use(VTooltip, {
defaultContainer: 'bdtooltips', defaultContainer: 'bd-tooltips',
defaultClass: 'bd-tooltip', defaultClass: 'bd-tooltip',
defaultTargetClass: 'bd-has-tooltip', defaultTargetClass: 'bd-has-tooltip',
defaultInnerSelector: '.bd-tooltip-inner', defaultInnerSelector: '.bd-tooltip-inner',

View File

@ -17,6 +17,7 @@ module.exports = (Plugin, Api, Vendor) => {
onStop() { onStop() {
Events.unsubscribeAll(); Events.unsubscribeAll();
Logger.log('onStop'); Logger.log('onStop');
console.log(this.showSettingsModal());
return true; return true;
} }