content -> addon

This commit is contained in:
Zack Rauen 2019-06-27 16:18:40 -04:00
parent 62e8771495
commit 1e084d31b4
18 changed files with 726 additions and 600 deletions

View File

@ -171,7 +171,7 @@
background-color: rgb(46,154,74); background-color: rgb(46,154,74);
} }
.bd-version { #bbd-version {
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
color: #72767d; color: #72767d;
@ -539,7 +539,7 @@
border-radius: 0 0 5px 5px; border-radius: 0 0 5px 5px;
} }
#bda-qem { #bd-qem {
border-radius: 5px 5px 0 0; border-radius: 5px 5px 0 0;
background: #FFF; background: #FFF;
border-bottom: 1px solid rgba(0, 0, 0, 0.1) !important; border-bottom: 1px solid rgba(0, 0, 0, 0.1) !important;
@ -548,42 +548,42 @@
flex-direction: row; flex-direction: row;
padding-right: 1px !important; padding-right: 1px !important;
} }
#bda-qem button { #bd-qem button {
border-left: 1px solid #EFEFEF; border-left: 1px solid #EFEFEF;
background: transparent; background: transparent;
box-shadow: #CECECE 1px 0 0 0; box-shadow: #CECECE 1px 0 0 0;
flex-grow: 1; flex-grow: 1;
} }
#bda-qem button:hover { #bd-qem button:hover {
background: #ECECEC; background: #ECECEC;
} }
#bda-qem-twitch { #bd-qem-twitch {
border-radius: 5px 0 0 0; border-radius: 5px 0 0 0;
order: 2; order: 2;
} }
#bda-qem-emojis { #bd-qem-emojis {
border-radius: 0 5px 0 0; border-radius: 0 5px 0 0;
order: 3; order: 3;
} }
#bda-qem-favourite { #bd-qem-favourite {
order: 3; order: 3;
} }
#bda-qem button.active { #bd-qem button.active {
background-color: #E2E2E2; background-color: #E2E2E2;
} }
#bda-qem-twitch-container, #bda-qem-favourite-container { #bd-qem-twitch-container, #bd-qem-favourite-container {
width: 346px; width: 346px;
height: 329px; height: 329px;
background-color: #FFF; background-color: #FFF;
border-radius: 0 0 5px 5px; border-radius: 0 0 5px 5px;
} }
#bda-qem-twitch-container .scroller-wrap, #bda-qem-favourite-container .scroller-wrap { #bd-qem-twitch-container .scroller-wrap, #bd-qem-favourite-container .scroller-wrap {
height: 100%; height: 100%;
} }
@ -591,7 +591,7 @@
padding: 5px 0 0 15px; padding: 5px 0 0 15px;
} }
.bda-qme-hidden #bda-qem-emojis { .bd-qme-hidden #bd-qem-emojis {
display: none; display: none;
} }
/* ================ */ /* ================ */
@ -632,7 +632,7 @@
margin-right: 10px; margin-right: 10px;
} }
.ui-card.ui-card-primary.bd-server-card:first-child { /* .ui-card.ui-card-primary.bd-server-card:first-child {
margin-bottom: 13px; margin-bottom: 13px;
} }
@ -644,7 +644,7 @@
left: 0; left: 0;
right: 0; right: 0;
margin-top: 4px; margin-top: 4px;
} } */
.bd-server-card.bd-server-card-pinned { .bd-server-card.bd-server-card-pinned {
margin-bottom: 15px; margin-bottom: 15px;
@ -716,7 +716,7 @@
top: 0; top: 0;
} }
#pubslayer .ui-tab-bar-item { /* #pubslayer .ui-tab-bar-item {
color: #b9bbbe; color: #b9bbbe;
padding-top: 6px; padding-top: 6px;
padding-bottom: 6px; padding-bottom: 6px;
@ -774,7 +774,7 @@
#pubslayer h5.ui-form-title { #pubslayer h5.ui-form-title {
color: #f6f6f7; color: #f6f6f7;
} } */
#pubslayer button { #pubslayer button {
background: #7289da; background: #7289da;
@ -1002,7 +1002,7 @@ body .ace_closeButton:active {
margin-left: 10px; margin-left: 10px;
} }
#bd-settings-sidebar .ui-tab-bar-item { /* #bd-settings-sidebar .ui-tab-bar-item {
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
line-height: 20px; line-height: 20px;
@ -1087,27 +1087,27 @@ body .ace_closeButton:active {
} }
.theme-light #bd-settingspane-container h2.ui-form-title { .theme-light #bd-settingspane-container h2.ui-form-title {
color: #4f545c; color: #4f545c;
} } */
.ui-switch-item { .bd-switch-item {
flex-direction: column; flex-direction: column;
margin-top: 8px; margin-top: 8px;
} }
.ui-switch-item h3 { .bd-switch-item h3 {
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
line-height: 24px; line-height: 24px;
flex: 1; flex: 1;
} }
.theme-dark .ui-switch-item h3 { .theme-dark .bd-switch-item h3 {
color: #f6f6f7; color: #f6f6f7;
} }
.theme-light .ui-switch-item h3 { .theme-light .bd-switch-item h3 {
color: #4f545c; color: #4f545c;
} }
.ui-switch-item .style-description { /* .ui-switch-item .style-description {
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: 20px; line-height: 20px;
@ -1120,9 +1120,9 @@ body .ace_closeButton:active {
} }
.theme-light .ui-switch-item .style-description { .theme-light .ui-switch-item .style-description {
color: rgba(114,118,125,.6); color: rgba(114,118,125,.6);
} } */
.ui-switch-item .ui-switch-wrapper { .bd-switch-item .bd-switch-wrapper {
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
-ms-user-select: none; -ms-user-select: none;
@ -1134,7 +1134,7 @@ body .ace_closeButton:active {
flex: 0 0 auto; flex: 0 0 auto;
} }
.ui-switch-item .ui-switch-wrapper input { .bd-switch-item .bd-switch-wrapper input {
position: absolute; position: absolute;
opacity: 0; opacity: 0;
cursor: pointer; cursor: pointer;
@ -1143,7 +1143,7 @@ body .ace_closeButton:active {
z-index: 1; z-index: 1;
} }
.ui-switch-item .ui-switch-wrapper .ui-switch { .bd-switch-item .bd-switch-wrapper .bd-switch {
background: #7289da; background: #7289da;
position: absolute; position: absolute;
top: 0; top: 0;
@ -1155,7 +1155,7 @@ body .ace_closeButton:active {
transition: background .15s ease-in-out,box-shadow .15s ease-in-out,border .15s ease-in-out; transition: background .15s ease-in-out,box-shadow .15s ease-in-out,border .15s ease-in-out;
} }
.ui-switch-item .ui-switch-wrapper .ui-switch:before { .bd-switch-item .bd-switch-wrapper .bd-switch:before {
content: ""; content: "";
display: block; display: block;
width: 18px; width: 18px;
@ -1170,11 +1170,11 @@ body .ace_closeButton:active {
box-shadow: 0 3px 1px 0 rgba(0,0,0,.05),0 2px 2px 0 rgba(0,0,0,.1),0 3px 3px 0 rgba(0,0,0,.05); box-shadow: 0 3px 1px 0 rgba(0,0,0,.05),0 2px 2px 0 rgba(0,0,0,.1),0 3px 3px 0 rgba(0,0,0,.05);
} }
.ui-switch-item .ui-switch-wrapper .ui-switch.checked { .bd-switch-item .bd-switch-wrapper .bd-switch.checked {
background: #7289da; background: #7289da;
} }
.ui-switch-item .ui-switch-wrapper .ui-switch.checked:before { .bd-switch-item .bd-switch-wrapper .bd-switch.checked:before {
transform: translateX(20px); transform: translateX(20px);
} }
@ -1185,9 +1185,9 @@ body .ace_closeButton:active {
#bd-settingspane-container .scroller-wrap .scroller { #bd-settingspane-container .scroller-wrap .scroller {
display: flex; display: flex;
} }
.content-column .ui-form-title:first-child { /* .content-column .ui-form-title:first-child {
margin-top: 0; margin-top: 0;
} } */
/* ================= */ /* ================= */
/* END BD SETTINGS */ /* END BD SETTINGS */
@ -1215,15 +1215,15 @@ body .ace_closeButton:active {
margin-right: 5px; margin-right: 5px;
} }
.bda-controls { .bd-controls {
display: flex; display: flex;
} }
.bda-slist { .bd-slist {
user-select: text; user-select: text;
} }
.bda-slist li { .bd-slist li {
max-height: 175px; max-height: 175px;
margin-bottom: 20px; margin-bottom: 20px;
padding: 5px 8px; padding: 5px 8px;
@ -1231,23 +1231,23 @@ body .ace_closeButton:active {
border-radius: 5px; border-radius: 5px;
overflow: hidden; overflow: hidden;
} }
.theme-dark .bda-slist li { .theme-dark .bd-slist li {
background-color: rgba(32,34,37,.6); background-color: rgba(32,34,37,.6);
color: #f6f6f7; color: #f6f6f7;
border-color: #202225; border-color: #202225;
} }
.theme-light .bda-slist li { .theme-light .bd-slist li {
background-color: #f8f9f9; background-color: #f8f9f9;
color: #4f545c; color: #4f545c;
border-color: #dcddde; border-color: #dcddde;
} }
.bda-slist li.settings-open { .bd-slist li.settings-open {
max-height: 800px; max-height: 800px;
overflow-y: auto; overflow-y: auto;
} }
.bda-slist .bda-header { .bd-slist .bd-header {
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
display: flex; display: flex;
@ -1257,36 +1257,36 @@ body .ace_closeButton:active {
border-bottom: 1px solid transparent; border-bottom: 1px solid transparent;
overflow: hidden; overflow: hidden;
} }
.theme-dark .bda-slist .bda-header { .theme-dark .bd-slist .bd-header {
color: #f6f6f7; color: #f6f6f7;
border-bottom-color: rgba(114,118,125,.3); border-bottom-color: rgba(114,118,125,.3);
} }
.theme-light .bda-slist .bda-header { .theme-light .bd-slist .bd-header {
color: #4f545c; color: #4f545c;
border-bottom-color: rgba(185,187,190,.3); border-bottom-color: rgba(185,187,190,.3);
} }
.bda-slist .bda-description { .bd-slist .bd-description {
word-break: break-word; word-break: break-word;
max-height: 100px; max-height: 100px;
margin: 5px 0; margin: 5px 0;
padding: 5px 0; padding: 5px 0;
overflow-y: auto; overflow-y: auto;
} }
.theme-dark .bda-slist .bda-description { .theme-dark .bd-slist .bd-description {
color: #b9bbbe; color: #b9bbbe;
} }
.theme-light .bda-slist .bda-description { .theme-light .bd-slist .bd-description {
color: #72767d; color: #72767d;
} }
.bda-slist .scroller::-webkit-scrollbar-track-piece, .bd-slist .scroller::-webkit-scrollbar-track-piece,
.bda-slist .scroller::-webkit-scrollbar-thumb { .bd-slist .scroller::-webkit-scrollbar-thumb {
border-radius:0 !important; border-radius:0 !important;
border-color:transparent; border-color:transparent;
} }
.bda-slist .bda-footer { .bd-slist .bd-footer {
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
display: flex; display: flex;
@ -1296,14 +1296,14 @@ body .ace_closeButton:active {
border-top: 1px solid transparent; border-top: 1px solid transparent;
overflow: hidden; overflow: hidden;
} }
.theme-dark .bda-slist .bda-footer { .theme-dark .bd-slist .bd-footer {
border-top-color: rgba(114,118,125,.3); border-top-color: rgba(114,118,125,.3);
} }
.theme-light .bda-slist .bda-footer { .theme-light .bd-slist .bd-footer {
border-top-color: rgba(185,187,190,.3); border-top-color: rgba(185,187,190,.3);
} }
.bda-slist .bda-footer button { .bd-slist .bd-footer button {
background: #7289da; background: #7289da;
color: #FFF; color: #FFF;
border-radius: 5px; border-radius: 5px;
@ -1313,15 +1313,15 @@ body .ace_closeButton:active {
transition: opacity 250ms ease; transition: opacity 250ms ease;
} }
.bda-slist .bda-footer button:disabled { .bd-slist .bd-footer button:disabled {
opacity: 0.4; opacity: 0.4;
} }
.bda-slist .bda-footer a { .bd-slist .bd-footer a {
color: #7289da; color: #7289da;
} }
.bda-slist .bda-footer a:hover { .bd-slist .bd-footer a:hover {
text-decoration: underline; text-decoration: underline;
} }
/* ======================= */ /* ======================= */
@ -1914,101 +1914,101 @@ body .ace_closeButton:active {
/* BEGIN DARK MODE */ /* BEGIN DARK MODE */
/* =============== */ /* =============== */
/* Emoji Picker */ /* Emoji Picker */
.bda-dark #bda-qem-favourite-container, .bd-dark #bd-qem-favourite-container,
.bda-dark #bda-qem-twitch-container { .bd-dark #bd-qem-twitch-container {
background-color: #353535; background-color: #353535;
} }
.bda-dark #bda-qem { .bd-dark #bd-qem {
border-bottom: 1px solid #464646 !important; border-bottom: 1px solid #464646 !important;
background: #353535; background: #353535;
} }
.bda-dark #bda-qem button { .bd-dark #bd-qem button {
background: #353535; background: #353535;
border-left: 1px solid #242424; border-left: 1px solid #242424;
box-shadow: #424242 1px 0 0 0; box-shadow: #424242 1px 0 0 0;
color: #FFF; color: #FFF;
} }
.bda-dark #bda-qem button.active { .bd-dark #bd-qem button.active {
background-color: #292929; background-color: #292929;
} }
.bda-dark #bda-qem button:hover { .bd-dark #bd-qem button:hover {
background-color: #303030; background-color: #303030;
} }
.bda-dark #bda-qem-favourite-container, .bd-dark #bd-qem-favourite-container,
.bda-dark #bda-qem-twitch-container { .bd-dark #bd-qem-twitch-container {
background-color: #353535; background-color: #353535;
} }
.bda-dark .emojiPicker-3m1S-j { .bd-dark .emojiPicker-3m1S-j {
background-color: #353535; background-color: #353535;
} }
.bda-dark .emojiPicker-3m1S-j .category-2U57w6 { .bd-dark .emojiPicker-3m1S-j .category-2U57w6 {
background-color: #353535; background-color: #353535;
} }
.bda-dark .emojiPicker-3m1S-j .header-1nkwgG .searchBar-2pWH0_ { .bd-dark .emojiPicker-3m1S-j .header-1nkwgG .searchBar-2pWH0_ {
background-color: #2B2B2B; background-color: #2B2B2B;
} }
.bda-dark .emojiPicker-3m1S-j .searchBar-2pWH0_ input { .bd-dark .emojiPicker-3m1S-j .searchBar-2pWH0_ input {
color: #FFF; color: #FFF;
} }
.bda-dark .emojiPicker-3m1S-j .searchBar-2pWH0_ input::-webkit-input-placeholder { .bd-dark .emojiPicker-3m1S-j .searchBar-2pWH0_ input::-webkit-input-placeholder {
color: #FFF; color: #FFF;
} }
.bda-dark .emojiPicker-3m1S-j .scroller-3vODG7 .emojiItem-109bjA.selected-39BZ4S { .bd-dark .emojiPicker-3m1S-j .scroller-3vODG7 .emojiItem-109bjA.selected-39BZ4S {
background-color: rgba(123, 123, 123, 0.37); background-color: rgba(123, 123, 123, 0.37);
} }
.bda-dark .emojiPicker-3m1S-j .dimmer-3iH-5D.visible-3k45bQ { .bd-dark .emojiPicker-3m1S-j .dimmer-3iH-5D.visible-3k45bQ {
background-color: rgba(62, 62, 62, 0.65); background-color: rgba(62, 62, 62, 0.65);
} }
.bda-dark .emojiPicker-3m1S-j .diversitySelector-tmmMv0 .popout-2nUePc { .bd-dark .emojiPicker-3m1S-j .diversitySelector-tmmMv0 .popout-2nUePc {
background: #353535; background: #353535;
border-color: #202020; border-color: #202020;
} }
.bda-dark #bda-qem-favourite-container .scroller::-webkit-scrollbar, .bd-dark #bd-qem-favourite-container .scroller::-webkit-scrollbar,
.bda-dark #bda-qem-favourite-container .scroller::-webkit-scrollbar-track, .bd-dark #bd-qem-favourite-container .scroller::-webkit-scrollbar-track,
.bda-dark #bda-qem-favourite-container .scroller::-webkit-scrollbar-track-piece, .bd-dark #bd-qem-favourite-container .scroller::-webkit-scrollbar-track-piece,
.bda-dark #bda-qem-twitch-container .scroller::-webkit-scrollbar, .bd-dark #bd-qem-twitch-container .scroller::-webkit-scrollbar,
.bda-dark #bda-qem-twitch-container .scroller::-webkit-scrollbar-track, .bd-dark #bd-qem-twitch-container .scroller::-webkit-scrollbar-track,
.bda-dark #bda-qem-twitch-container .scroller::-webkit-scrollbar-track-piece, .bd-dark #bd-qem-twitch-container .scroller::-webkit-scrollbar-track-piece,
.bda-dark .emojiPicker-3m1S-j .scroller-3vODG7::-webkit-scrollbar, .bd-dark .emojiPicker-3m1S-j .scroller-3vODG7::-webkit-scrollbar,
.bda-dark .emojiPicker-3m1S-j .scroller-3vODG7::-webkit-scrollbar-track, .bd-dark .emojiPicker-3m1S-j .scroller-3vODG7::-webkit-scrollbar-track,
.bda-dark .emojiPicker-3m1S-j .scroller-3vODG7::-webkit-scrollbar-track-piece { .bd-dark .emojiPicker-3m1S-j .scroller-3vODG7::-webkit-scrollbar-track-piece {
background-color: #303030 !important; background-color: #303030 !important;
border-color: #303030 !important; border-color: #303030 !important;
} }
.bda-dark #bda-qem-twitch-container .scroller::-webkit-scrollbar-thumb, .bd-dark #bd-qem-twitch-container .scroller::-webkit-scrollbar-thumb,
.bda-dark #bda-qem-favourite-container .scroller::-webkit-scrollbar-thumb, .bd-dark #bd-qem-favourite-container .scroller::-webkit-scrollbar-thumb,
.bda-dark .emojiPicker-3g68GS .scroller-3vODG7::-webkit-scrollbar-thumb { .bd-dark .emojiPicker-3g68GS .scroller-3vODG7::-webkit-scrollbar-thumb {
border-color: #202020 !important; border-color: #202020 !important;
background-color: #202020 !important; background-color: #202020 !important;
} }
/* add/create server */ /* add/create server */
.bda-dark .theme-light .slide-2pHaq5 { .bd-dark .theme-light .slide-2pHaq5 {
background: #36393f; background: #36393f;
} }
.bda-dark .theme-dark .action-1lSjCi, .bd-dark .theme-dark .action-1lSjCi,
.bda-dark .theme-light .action-1lSjCi { .bd-dark .theme-light .action-1lSjCi {
background: #2F3136; background: #2F3136;
} }
/* centered or */ /* centered or */
.bda-dark .theme-dark .or-3THJsp, .bd-dark .theme-dark .or-3THJsp,
.bda-dark .theme-light .or-3THJsp { .bd-dark .theme-light .or-3THJsp {
background: #2F3136; background: #2F3136;
order: 2; order: 2;
height: 56px; height: 56px;
@ -2020,48 +2020,48 @@ body .ace_closeButton:active {
border: 2px solid #484B52; border: 2px solid #484B52;
} }
.bda-dark .create-3jownz { .bd-dark .create-3jownz {
order: 1; order: 1;
} }
.bda-dark .join-33Tr-7 { .bd-dark .join-33Tr-7 {
order: 3; order: 3;
} }
.bda-dark .theme-dark .actionIcon-2IISM_, .bd-dark .theme-dark .actionIcon-2IISM_,
.bda-dark .theme-light .actionIcon-2IISM_ { .bd-dark .theme-light .actionIcon-2IISM_ {
filter: grayscale(100%) brightness(60%); filter: grayscale(100%) brightness(60%);
} }
.bda-dark .theme-light .footer-2yfCgX { .bd-dark .theme-light .footer-2yfCgX {
background: #2F3136; background: #2F3136;
} }
/* Region Select */ /* Region Select */
.bda-dark .regionSelectModal-12e-57 { .bd-dark .regionSelectModal-12e-57 {
background: #36393f; background: #36393f;
} }
.bda-dark .regionSelectModal-12e-57 .regionSelectModalOption-2DSIZ3 { .bd-dark .regionSelectModal-12e-57 .regionSelectModalOption-2DSIZ3 {
background: #2F3136; background: #2F3136;
border: 2px solid #484B52; border: 2px solid #484B52;
} }
/* Ace Editor Settings */ /* Ace Editor Settings */
.bda-dark ~ div #ace_settingsmenu { .bd-dark ~ div #ace_settingsmenu {
color: #f6f6f7; color: #f6f6f7;
background: #36393f; background: #36393f;
box-shadow: 0 0 0 1px rgba(32,34,37,.6),0 2px 10px 0 rgba(0,0,0,.2); box-shadow: 0 0 0 1px rgba(32,34,37,.6),0 2px 10px 0 rgba(0,0,0,.2);
} }
.bda-dark ~ div #ace_settingsmenu select, .bd-dark ~ div #ace_settingsmenu select,
.bda-dark ~ div #ace_settingsmenu input[type="text"] { .bd-dark ~ div #ace_settingsmenu input[type="text"] {
color: #f6f6f7; color: #f6f6f7;
background: #2F3136; background: #2F3136;
border: 1px solid #484B52; border: 1px solid #484B52;
} }
.bda-dark ~ div .ace_closeButton::before { .bd-dark ~ div .ace_closeButton::before {
color: #f6f6f7; color: #f6f6f7;
} }

File diff suppressed because one or more lines are too long

View File

@ -6,10 +6,10 @@ export default new class DarkMode extends Builtin {
get id() {return "darkMode";} get id() {return "darkMode";}
enabled() { enabled() {
document.getElementById("app-mount").classList.add("bda-dark", "bda-dark"); document.getElementById("app-mount").classList.add("bda-dark", "bd-dark");
} }
disabled() { disabled() {
document.getElementById("app-mount").classList.remove("bda-dark", "bda-dark"); document.getElementById("app-mount").classList.remove("bda-dark", "bd-dark");
} }
}; };

View File

@ -3,13 +3,13 @@ import {Utilities, Events} from "modules";
import EmoteModule from "./emotes"; import EmoteModule from "./emotes";
const headerHTML = `<div id="bda-qem"> const headerHTML = `<div id="bd-qem">
<button class="active" id="bda-qem-twitch">Twitch</button> <button class="active" id="bd-qem-twitch">Twitch</button>
<button id="bda-qem-favourite">Favourite</button> <button id="bd-qem-favourite">Favourite</button>
<button id="bda-qem-emojis">Emojis</buttond> <button id="bd-qem-emojis">Emojis</buttond>
</div>`; </div>`;
const twitchEmoteHTML = `<div id="bda-qem-twitch-container"> const twitchEmoteHTML = `<div id="bd-qem-twitch-container">
<div class="scroller-wrap scrollerWrap-2lJEkd fade"> <div class="scroller-wrap scrollerWrap-2lJEkd fade">
<div class="scroller scroller-2FKFPG"> <div class="scroller scroller-2FKFPG">
<div class="emote-menu-inner"> <div class="emote-menu-inner">
@ -19,7 +19,7 @@ const twitchEmoteHTML = `<div id="bda-qem-twitch-container">
</div> </div>
</div>`; </div>`;
const favoritesHTML = `<div id="bda-qem-favourite-container"> const favoritesHTML = `<div id="bd-qem-favourite-container">
<div class="scroller-wrap scrollerWrap-2lJEkd fade"> <div class="scroller-wrap scrollerWrap-2lJEkd fade">
<div class="scroller scroller-2FKFPG"> <div class="scroller scroller-2FKFPG">
<div class="emote-menu-inner"> <div class="emote-menu-inner">
@ -49,7 +49,7 @@ export default new class EmoteMenu extends Builtin {
constructor() { constructor() {
super(); super();
this.lastTab = "bda-qem-emojis"; this.lastTab = "bd-qem-emojis";
this.qmeHeader = Utilities.parseHTML(headerHTML); this.qmeHeader = Utilities.parseHTML(headerHTML);
for (const button of this.qmeHeader.getElementsByTagName("button")) button.addEventListener("click", this.switchMenu.bind(this)); for (const button of this.qmeHeader.getElementsByTagName("button")) button.addEventListener("click", this.switchMenu.bind(this));
@ -87,12 +87,12 @@ export default new class EmoteMenu extends Builtin {
enableHideEmojis() { enableHideEmojis() {
const picker = document.querySelector(".emojiPicker-3m1S-j"); const picker = document.querySelector(".emojiPicker-3m1S-j");
if (picker) picker.classList.add("bda-qme-hidden"); if (picker) picker.classList.add("bd-qme-hidden");
} }
disableHideEmojis() { disableHideEmojis() {
const picker = document.querySelector(".emojiPicker-3m1S-j"); const picker = document.querySelector(".emojiPicker-3m1S-j");
if (picker) picker.classList.remove("bda-qme-hidden"); if (picker) picker.classList.remove("bd-qme-hidden");
} }
insertEmote(emote) { insertEmote(emote) {
@ -105,8 +105,8 @@ export default new class EmoteMenu extends Builtin {
const em = e.target.closest(".emote-container").children[0]; const em = e.target.closest(".emote-container").children[0];
const menu = $(`<div id="removemenu" class="bd-context-menu context-menu theme-dark">Remove</div>`); const menu = $(`<div id="removemenu" class="bd-context-menu context-menu theme-dark">Remove</div>`);
menu.css({ menu.css({
top: e.pageY - $("#bda-qem-favourite-container").offset().top, top: e.pageY - $("#bd-qem-favourite-container").offset().top,
left: e.pageX - $("#bda-qem-favourite-container").offset().left left: e.pageX - $("#bd-qem-favourite-container").offset().left
}); });
$(em).parent().append(menu); $(em).parent().append(menu);
menu.on("click", (event) => { menu.on("click", (event) => {
@ -126,28 +126,28 @@ export default new class EmoteMenu extends Builtin {
switchMenu(e) { switchMenu(e) {
let id = typeof(e) == "string" ? e : e.target.id; let id = typeof(e) == "string" ? e : e.target.id;
if (id == "bda-qem-emojis" && this.hideEmojis) id = "bda-qem-favourite"; if (id == "bd-qem-emojis" && this.hideEmojis) id = "bd-qem-favourite";
const twitch = $("#bda-qem-twitch"); const twitch = $("#bd-qem-twitch");
const fav = $("#bda-qem-favourite"); const fav = $("#bd-qem-favourite");
const emojis = $("#bda-qem-emojis"); const emojis = $("#bd-qem-emojis");
twitch.removeClass("active"); twitch.removeClass("active");
fav.removeClass("active"); fav.removeClass("active");
emojis.removeClass("active"); emojis.removeClass("active");
$(".emojiPicker-3m1S-j").hide(); $(".emojiPicker-3m1S-j").hide();
$("#bda-qem-favourite-container").hide(); $("#bd-qem-favourite-container").hide();
$("#bda-qem-twitch-container").hide(); $("#bd-qem-twitch-container").hide();
switch (id) { switch (id) {
case "bda-qem-twitch": case "bd-qem-twitch":
twitch.addClass("active"); twitch.addClass("active");
$("#bda-qem-twitch-container").show(); $("#bd-qem-twitch-container").show();
break; break;
case "bda-qem-favourite": case "bd-qem-favourite":
fav.addClass("active"); fav.addClass("active");
$("#bda-qem-favourite-container").show(); $("#bd-qem-favourite-container").show();
break; break;
case "bda-qem-emojis": case "bd-qem-emojis":
emojis.addClass("active"); emojis.addClass("active");
$(".emojiPicker-3m1S-j").show(); $(".emojiPicker-3m1S-j").show();
$(".emojiPicker-3m1S-j input").focus(); $(".emojiPicker-3m1S-j input").focus();
@ -162,8 +162,8 @@ export default new class EmoteMenu extends Builtin {
if (!node.classList.contains("popout-3sVMXz") || node.classList.contains("popoutLeft-30WmrD") || !node.getElementsByClassName("emojiPicker-3m1S-j").length) return; if (!node.classList.contains("popout-3sVMXz") || node.classList.contains("popoutLeft-30WmrD") || !node.getElementsByClassName("emojiPicker-3m1S-j").length) return;
const e = $(node); const e = $(node);
if (this.hideEmojis) e.addClass("bda-qme-hidden"); if (this.hideEmojis) e.addClass("bd-qme-hidden");
else e.removeClass("bda-qme-hidden"); else e.removeClass("bd-qme-hidden");
e.prepend(this.qmeHeader); e.prepend(this.qmeHeader);
e.append(this.teContainer); e.append(this.teContainer);

View File

@ -61,7 +61,7 @@ function patchModuleLoad() {
const load = Module._load; const load = Module._load;
// const resolveFilename = Module._resolveFilename; // const resolveFilename = Module._resolveFilename;
Module._load = function (request) { Module._load = function(request) {
if (request === namespace || request.startsWith(prefix)) { if (request === namespace || request.startsWith(prefix)) {
const requested = request.substr(prefix.length); const requested = request.substr(prefix.length);
if (requested == "api") return BdApi; if (requested == "api") return BdApi;
@ -86,13 +86,5 @@ function patchModuleLoad() {
patchModuleLoad(); patchModuleLoad();
// export function getPluginByModule(module) {
// return this.localContent.find(plugin => module.filename === plugin.contentPath || module.filename.startsWith(plugin.contentPath + path.sep));
// }
// export function getPluginPathByModule(module) {
// return Object.keys(this.pluginApiInstances).find(contentPath => module.filename === contentPath || module.filename.startsWith(contentPath + path.sep));
// }
// var settingsPanel, emoteModule, quickEmoteMenu, voiceMode,, dMode, publicServersModule; // var settingsPanel, emoteModule, quickEmoteMenu, voiceMode,, dMode, publicServersModule;
// var bdConfig = null; // var bdConfig = null;

257
src/modules/addonmanager.js Normal file
View File

@ -0,0 +1,257 @@
import Utilities from "./utilities";
import Logger from "./logger";
import Settings from "./settingsmanager";
import Events from "./emitter";
import DataStore from "./datastore";
import AddonError from "../structs/addonerror";
import MetaError from "../structs/metaerror";
import Toasts from "../ui/toasts";
const path = require("path");
const fs = require("fs");
const Module = require("module").Module;
Module.globalPaths.push(path.resolve(require("electron").remote.app.getAppPath(), "node_modules"));
const splitRegex = /[^\S\r\n]*?\n[^\S\r\n]*?\*[^\S\r\n]?/;
const escapedAtRegex = /^\\@/;
const stripBOM = function(fileContent) {
if (fileContent.charCodeAt(0) === 0xFEFF) {
fileContent = fileContent.slice(1);
}
return fileContent;
};
export default class AddonManager {
get name() {return "";}
get moduleExtension() {return "";}
get extension() {return "";}
get addonFolder() {return "";}
get prefix() {return "addon";}
get collection() {return "settings";}
get category() {return "addons";}
get id() {return "autoReload";}
emit(event, ...args) {return Events.emit(`${this.prefix}-${event}`, ...args);}
constructor() {
this.timeCache = {};
this.addonList = [];
this.state = {};
}
initialize() {
this.originalRequire = Module._extensions[this.moduleExtension];
Module._extensions[this.moduleExtension] = this.getAddonRequire();
Settings.on(this.collection, this.category, this.id, (enabled) => {
if (enabled) this.watchAddons();
else this.unwatchAddons();
});
return this.loadAllAddons();
}
// Subclasses should overload this and modify the addon object as needed to fully load it
initializeAddon() {return;}
// Subclasses should overload this and modify the fileContent as needed to require() the file
getFileModification(module, fileContent) {return fileContent;}
startAddon() {return;}
stopAddon() {return;}
loadState() {
const saved = DataStore.getData(`${this.prefix}s`);
if (!saved) return;
Object.assign(this.state, saved);
}
saveState() {
DataStore.setData(`${this.prefix}s`, this.state);
}
watchAddons() {
if (this.watcher) return Logger.error(this.name, `Already watching ${this.prefix} addons.`);
Logger.log(this.name, `Starting to watch ${this.prefix} addons.`);
this.watcher = fs.watch(this.addonFolder, {persistent: false}, async (eventType, filename) => {
if (!eventType || !filename || !filename.endsWith(this.extension)) return;
await new Promise(r => setTimeout(r, 50));
try {fs.statSync(path.resolve(this.addonFolder, filename));}
catch (err) {
if (err.code !== "ENOENT") return;
delete this.timeCache[filename];
this.unloadAddon(filename, true);
}
if (!fs.statSync(path.resolve(this.addonFolder, filename)).isFile()) return;
const stats = fs.statSync(path.resolve(this.addonFolder, filename));
if (!stats || !stats.mtime || !stats.mtime.getTime()) return;
if (typeof(stats.mtime.getTime()) !== "number") return;
if (this.timeCache[filename] == stats.mtime.getTime()) return;
this.timeCache[filename] = stats.mtime.getTime();
if (eventType == "rename") this.loadAddon(filename, true);
if (eventType == "change") this.reloadAddon(filename, true);
});
}
unwatchAddons() {
if (!this.watcher) return Logger.error(this.name, `Was not watching ${this.prefix} addons.`);
this.watcher.close();
delete this.watcher;
Logger.log(this.name, `No longer watching ${this.prefix} addons.`);
}
extractMeta(fileContent) {
const firstLine = fileContent.split("\n")[0];
const hasOldMeta = firstLine.includes("//META");
if (hasOldMeta) return this.parseOldMeta(fileContent);
const hasNewMeta = firstLine.includes("/**");
if (hasNewMeta) return this.parseNewMeta(fileContent);
throw new MetaError("META was not found.");
}
parseOldMeta(fileContent) {
const meta = fileContent.split("\n")[0];
const metaData = meta.substring(meta.lastIndexOf("//META") + 6, meta.lastIndexOf("*//"));
const parsed = Utilities.testJSON(metaData);
if (!parsed) throw new MetaError("META could not be parsed.");
if (!parsed.name) throw new MetaError("META missing name data.");
return parsed;
}
parseNewMeta(fileContent) {
const block = fileContent.split("/**", 2)[1].split("*/", 1)[0];
const out = {};
let field = "";
let accum = "";
for (const line of block.split(splitRegex)) {
if (line.length === 0) continue;
if (line.charAt(0) === "@" && line.charAt(1) !== " ") {
out[field] = accum;
const l = line.indexOf(" ");
field = line.substr(1, l - 1);
accum = line.substr(l + 1);
}
else {
accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@");
}
}
out[field] = accum.trim();
delete out[""];
return out;
}
getAddonRequire() {
const self = this;
// const baseFolder = this.addonFolder;
const originalRequire = this.originalRequire;
return function(module, filename) {
const possiblePath = path.resolve(self.addonFolder, path.basename(filename));
if (!fs.existsSync(possiblePath) || filename !== fs.realpathSync(possiblePath)) return Reflect.apply(originalRequire, this, arguments);
let fileContent = fs.readFileSync(filename, "utf8");
fileContent = stripBOM(fileContent);
const meta = self.extractMeta(fileContent);
meta.id = meta.name;
meta.filename = path.basename(filename);
fileContent = self.getFileModification(module, fileContent, meta);
module._compile(fileContent, filename);
};
}
// Subclasses should use the return (if not AddonError) and push to this.addonList
loadAddon(filename, shouldToast = false) {
if (typeof(filename) === "undefined") return;
try {__non_webpack_require__(path.resolve(this.addonFolder, filename));}
catch (error) {return new AddonError(filename, filename, "Could not be compiled.", {message: error.message, stack: error.stack});}
const addon = __non_webpack_require__(path.resolve(this.addonFolder, filename));
if (this.addonList.find(c => c.id == addon.id)) return new AddonError(addon.name, filename, `There is already a plugin with name ${addon.name}`);
const error = this.initializeAddon(addon);
if (error) return error;
this.addonList.push(addon);
if (shouldToast) Toasts.success(`${addon.name} v${addon.version} was loaded.`);
this.emit("loaded", addon.id);
if (!this.state[addon.id]) return this.state[addon.id] = false;
return this.startAddon(addon);
}
unloadAddon(idOrFileOrAddon, shouldToast = true, isReload = false) {
const addon = typeof(idOrFileOrAddon) == "string" ? this.addonList.find(c => c.id == idOrFileOrAddon || c.filename == idOrFileOrAddon) : idOrFileOrAddon;
if (!addon) return false;
if (this.state[addon.id]) isReload ? this.stopAddon(addon) : this.disableAddon(addon);
delete __non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(this.addonFolder, addon.filename))];
this.addonList.splice(this.addonList.indexOf(addon), 1);
this.emit("unloaded", addon.id);
if (shouldToast) Toasts.success(`${addon.name} was unloaded.`);
return true;
}
reloadAddon(idOrFileOrAddon, shouldToast = true) {
const addon = typeof(idOrFileOrAddon) == "string" ? this.addonList.find(c => c.id == idOrFileOrAddon || c.filename == idOrFileOrAddon) : idOrFileOrAddon;
const didUnload = this.unloadAddon(addon, shouldToast, true);
if (!didUnload) return didUnload;
return this.loadAddon(addon.filename, shouldToast);
}
isLoaded(idOrFile) {
const addon = this.addonList.find(c => c.id == idOrFile || c.filename == idOrFile);
if (!addon) return false;
return true;
}
isEnabled(idOrFile) {
const addon = this.addonList.find(c => c.id == idOrFile || c.filename == idOrFile);
if (!addon) return false;
return this.state[addon.id];
}
enableAddon(idOrAddon) {
const addon = typeof(idOrAddon) == "string" ? this.addonList.find(p => p.id == idOrAddon) : idOrAddon;
if (!addon) return;
if (this.state[addon.id]) return;
this.state[addon.id] = true;
this.startAddon(addon);
this.saveState();
}
disableAddon(idOrAddon) {
const addon = typeof(idOrAddon) == "string" ? this.addonList.find(p => p.id == idOrAddon) : idOrAddon;
if (!addon) return;
if (!this.state[addon.id]) return;
this.state[addon.id] = false;
this.stopAddon(addon);
this.saveState();
}
toggleAddon(id) {
if (this.state[id]) this.disableAddon(id);
else this.enableAddon(id);
}
loadNewAddons() {
const files = fs.readdirSync(this.addonFolder);
const removed = this.addonList.filter(t => !files.includes(t.filename)).map(c => c.id);
const added = files.filter(f => !this.addonList.find(t => t.filename == f) && f.endsWith(this.extension) && fs.statSync(path.resolve(this.addonFolder, f)).isFile());
return {added, removed};
}
updateList() {
const results = this.loadNewAddons();
for (const filename of results.added) this.loadAddon(filename);
for (const name of results.removed) this.unloadAddon(name);
}
loadAllAddons() {
this.loadState();
const errors = [];
const files = fs.readdirSync(this.addonFolder);
for (const filename of files) {
if (!fs.statSync(path.resolve(this.addonFolder, filename)).isFile() || !filename.endsWith(this.extension)) continue;
const addon = this.loadAddon(filename, false);
if (addon instanceof AddonError) errors.push(addon);
}
this.saveState();
if (Settings.get(this.collection, this.category, this.id)) this.watchAddons();
return errors;
}
}

View File

@ -1,257 +0,0 @@
import Utilities from "./utilities";
import Logger from "./logger";
import Settings from "./settingsmanager";
import Events from "./emitter";
import DataStore from "./datastore";
import ContentError from "../structs/contenterror";
import MetaError from "../structs/metaerror";
import Toasts from "../ui/toasts";
const path = require("path");
const fs = require("fs");
const Module = require("module").Module;
Module.globalPaths.push(path.resolve(require("electron").remote.app.getAppPath(), "node_modules"));
const splitRegex = /[^\S\r\n]*?\n[^\S\r\n]*?\*[^\S\r\n]?/;
const escapedAtRegex = /^\\@/;
const stripBOM = function(content) {
if (content.charCodeAt(0) === 0xFEFF) {
content = content.slice(1);
}
return content;
};
export default class AddonManager {
get name() {return "";}
get moduleExtension() {return "";}
get extension() {return "";}
get contentFolder() {return "";}
get prefix() {return "addon";}
get collection() {return "settings";}
get category() {return "addons";}
get id() {return "autoReload";}
emit(event, ...args) {return Events.emit(`${this.prefix}-${event}`, ...args);}
constructor() {
this.timeCache = {};
this.contentList = [];
this.state = {};
}
initialize() {
this.originalRequire = Module._extensions[this.moduleExtension];
Module._extensions[this.moduleExtension] = this.getContentRequire();
Settings.on(this.collection, this.category, this.id, (enabled) => {
if (enabled) this.watchContent();
else this.unwatchContent();
});
return this.loadAllContent();
}
// Subclasses should overload this and modify the content object as needed to fully load it
initializeContent() {return;}
// Subclasses should overload this and modify the content as needed to require() the file
getContentModification(module, content) {return content;}
startContent() {return;}
stopContent() {return;}
loadState() {
const saved = DataStore.getData(`${this.prefix}s`);
if (!saved) return;
Object.assign(this.state, saved);
}
saveState() {
DataStore.setData(`${this.prefix}s`, this.state);
}
watchContent() {
if (this.watcher) return Logger.error(this.name, "Already watching content.");
Logger.log(this.name, "Starting to watch content.");
this.watcher = fs.watch(this.contentFolder, {persistent: false}, async (eventType, filename) => {
if (!eventType || !filename || !filename.endsWith(this.extension)) return;
await new Promise(r => setTimeout(r, 50));
try {fs.statSync(path.resolve(this.contentFolder, filename));}
catch (err) {
if (err.code !== "ENOENT") return;
delete this.timeCache[filename];
this.unloadContent(filename, true);
}
if (!fs.statSync(path.resolve(this.contentFolder, filename)).isFile()) return;
const stats = fs.statSync(path.resolve(this.contentFolder, filename));
if (!stats || !stats.mtime || !stats.mtime.getTime()) return;
if (typeof(stats.mtime.getTime()) !== "number") return;
if (this.timeCache[filename] == stats.mtime.getTime()) return;
this.timeCache[filename] = stats.mtime.getTime();
if (eventType == "rename") this.loadContent(filename, true);
if (eventType == "change") this.reloadContent(filename, true);
});
}
unwatchContent() {
if (!this.watcher) return Logger.error(this.name, "Was not watching content.");
this.watcher.close();
delete this.watcher;
Logger.log(this.name, "No longer watching content.");
}
extractMeta(content) {
const firstLine = content.split("\n")[0];
const hasOldMeta = firstLine.includes("//META");
if (hasOldMeta) return this.parseOldMeta(content);
const hasNewMeta = firstLine.includes("/**");
if (hasNewMeta) return this.parseNewMeta(content);
throw new MetaError("META was not found.");
}
parseOldMeta(content) {
const meta = content.split("\n")[0];
const metaData = meta.substring(meta.lastIndexOf("//META") + 6, meta.lastIndexOf("*//"));
const parsed = Utilities.testJSON(metaData);
if (!parsed) throw new MetaError("META could not be parsed.");
if (!parsed.name) throw new MetaError("META missing name data.");
return parsed;
}
parseNewMeta(content) {
const block = content.split("/**", 2)[1].split("*/", 1)[0];
const out = {};
let field = "";
let accum = "";
for (const line of block.split(splitRegex)) {
if (line.length === 0) continue;
if (line.charAt(0) === "@" && line.charAt(1) !== " ") {
out[field] = accum;
const l = line.indexOf(" ");
field = line.substr(1, l - 1);
accum = line.substr(l + 1);
}
else {
accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@");
}
}
out[field] = accum.trim();
delete out[""];
return out;
}
getContentRequire() {
const self = this;
// const baseFolder = this.contentFolder;
const originalRequire = this.originalRequire;
return function(module, filename) {
const possiblePath = path.resolve(self.contentFolder, path.basename(filename));
if (!fs.existsSync(possiblePath) || filename !== fs.realpathSync(possiblePath)) return Reflect.apply(originalRequire, this, arguments);
let content = fs.readFileSync(filename, "utf8");
content = stripBOM(content);
const meta = self.extractMeta(content);
meta.id = meta.name;
meta.filename = path.basename(filename);
content = self.getContentModification(module, content, meta);
module._compile(content, filename);
};
}
// Subclasses should use the return (if not ContentError) and push to this.contentList
loadContent(filename, shouldToast = false) {
if (typeof(filename) === "undefined") return;
try {__non_webpack_require__(path.resolve(this.contentFolder, filename));}
catch (error) {return new ContentError(filename, filename, "Could not be compiled.", {message: error.message, stack: error.stack});}
const content = __non_webpack_require__(path.resolve(this.contentFolder, filename));
if (this.contentList.find(c => c.id == content.id)) return new ContentError(content.name, filename, `There is already a plugin with name ${content.name}`);
const error = this.initializeContent(content);
if (error) return error;
this.contentList.push(content);
if (shouldToast) Toasts.success(`${content.name} v${content.version} was loaded.`);
this.emit("loaded", content.id);
if (!this.state[content.id]) return this.state[content.id] = false;
return this.startContent(content);
}
unloadContent(idOrFileOrContent, shouldToast = true, isReload = false) {
const content = typeof(idOrFileOrContent) == "string" ? this.contentList.find(c => c.id == idOrFileOrContent || c.filename == idOrFileOrContent) : idOrFileOrContent;
if (!content) return false;
if (this.state[content.id]) isReload ? this.stopContent(content) : this.disableContent(content);
delete __non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(this.contentFolder, content.filename))];
this.contentList.splice(this.contentList.indexOf(content), 1);
this.emit("unloaded", content.id);
if (shouldToast) Toasts.success(`${content.name} was unloaded.`);
return true;
}
reloadContent(idOrFileOrContent, shouldToast = true) {
const content = typeof(idOrFileOrContent) == "string" ? this.contentList.find(c => c.id == idOrFileOrContent || c.filename == idOrFileOrContent) : idOrFileOrContent;
const didUnload = this.unloadContent(content, shouldToast, true);
if (!didUnload) return didUnload;
return this.loadContent(content.filename, shouldToast);
}
isLoaded(idOrFile) {
const content = this.contentList.find(c => c.id == idOrFile || c.filename == idOrFile);
if (!content) return false;
return true;
}
isEnabled(idOrFile) {
const content = this.contentList.find(c => c.id == idOrFile || c.filename == idOrFile);
if (!content) return false;
return this.state[content.id];
}
enableContent(idOrContent) {
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
if (!content) return;
if (this.state[content.id]) return;
this.state[content.id] = true;
this.startContent(content);
this.saveState();
}
disableContent(idOrContent) {
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent;
if (!content) return;
if (!this.state[content.id]) return;
this.state[content.id] = false;
this.stopContent(content);
this.saveState();
}
toggleContent(id) {
if (this.state[id]) this.disableContent(id);
else this.enableContent(id);
}
loadNewContent() {
const files = fs.readdirSync(this.contentFolder);
const removed = this.contentList.filter(t => !files.includes(t.filename)).map(c => c.id);
const added = files.filter(f => !this.contentList.find(t => t.filename == f) && f.endsWith(this.extension) && fs.statSync(path.resolve(this.contentFolder, f)).isFile());
return {added, removed};
}
updateList() {
const results = this.loadNewContent();
for (const filename of results.added) this.loadContent(filename);
for (const name of results.removed) this.unloadContent(name);
}
loadAllContent() {
this.loadState();
const errors = [];
const files = fs.readdirSync(this.contentFolder);
for (const filename of files) {
if (!fs.statSync(path.resolve(this.contentFolder, filename)).isFile() || !filename.endsWith(this.extension)) continue;
const content = this.loadContent(filename, false);
if (content instanceof ContentError) errors.push(content);
}
this.saveState();
if (Settings.get(this.collection, this.category, this.id)) this.watchContent();
return errors;
}
}

View File

@ -40,7 +40,7 @@ Core.prototype.init = async function() {
DataStore.initialize(); DataStore.initialize();
await LocaleManager.initialize(); await LocaleManager.initialize();
Logger.log("Startup", "Initializing Settings"); Logger.log("Startup", "Initializing Settings");
Settings.initialize(); Settings.initialize();
@ -62,7 +62,7 @@ Core.prototype.init = async function() {
// Show loading errors // Show loading errors
Logger.log("Startup", "Collecting Startup Errors"); Logger.log("Startup", "Collecting Startup Errors");
Modals.showContentErrors({plugins: pluginErrors, themes: themeErrors}); Modals.showAddonErrors({plugins: pluginErrors, themes: themeErrors});
}; };
Core.prototype.waitForGuilds = function() { Core.prototype.waitForGuilds = function() {

View File

@ -1,8 +1,8 @@
import {Config} from "data"; import {Config} from "data";
import Logger from "./logger"; import Logger from "./logger";
import ContentManager from "./contentmanager"; import AddonManager from "./addonmanager";
import Utilities from "./utilities"; import Utilities from "./utilities";
import ContentError from "../structs/contenterror"; import AddonError from "../structs/addonerror";
import Settings from "./settingsmanager"; import Settings from "./settingsmanager";
import Strings from "./strings"; import Strings from "./strings";
@ -13,11 +13,11 @@ import SettingsRenderer from "../ui/settings";
const path = require("path"); const path = require("path");
const electronRemote = require("electron").remote; const electronRemote = require("electron").remote;
export default new class PluginManager extends ContentManager { export default new class PluginManager extends AddonManager {
get name() {return "PluginManager";} get name() {return "PluginManager";}
get moduleExtension() {return ".js";} get moduleExtension() {return ".js";}
get extension() {return ".plugin.js";} get extension() {return ".plugin.js";}
get contentFolder() {return path.resolve(Config.dataPath, "plugins");} get addonFolder() {return path.resolve(Config.dataPath, "plugins");}
get prefix() {return "plugin";} get prefix() {return "plugin";}
constructor() { constructor() {
@ -33,8 +33,8 @@ export default new class PluginManager extends ContentManager {
initialize() { initialize() {
const errors = super.initialize(); const errors = super.initialize();
this.setupFunctions(); this.setupFunctions();
Settings.registerPanel("plugins", Strings.Panels.plugins, {element: () => SettingsRenderer.getContentPanel(Strings.Panels.plugins, this.contentList, this.state, { Settings.registerPanel("plugins", Strings.Panels.plugins, {element: () => SettingsRenderer.getAddonPanel(Strings.Panels.plugins, this.addonList, this.state, {
folder: this.contentFolder, folder: this.addonFolder,
onChange: this.togglePlugin.bind(this), onChange: this.togglePlugin.bind(this),
reload: this.reloadPlugin.bind(this), reload: this.reloadPlugin.bind(this),
refreshList: this.updatePluginList.bind(this) refreshList: this.updatePluginList.bind(this)
@ -44,92 +44,92 @@ export default new class PluginManager extends ContentManager {
/* Aliases */ /* Aliases */
updatePluginList() {return this.updateList();} updatePluginList() {return this.updateList();}
loadAllPlugins() {return this.loadAllContent();} loadAllPlugins() {return this.loadAllAddons();}
enablePlugin(idOrContent) {return this.enableContent(idOrContent);} enablePlugin(idOrAddon) {return this.enableAddon(idOrAddon);}
disablePlugin(idOrContent) {return this.disableContent(idOrContent);} disablePlugin(idOrAddon) {return this.disableAddon(idOrAddon);}
togglePlugin(id) {return this.toggleContent(id);} togglePlugin(id) {return this.toggleAddon(id);}
unloadPlugin(idOrFileOrContent) {return this.unloadContent(idOrFileOrContent);} unloadPlugin(idOrFileOrAddon) {return this.unloadAddon(idOrFileOrAddon);}
loadPlugin(filename) { loadPlugin(filename) {
const error = this.loadContent(filename); const error = this.loadAddon(filename);
if (error) Modals.showContentErrors({themes: [error]}); if (error) Modals.showAddonErrors({themes: [error]});
} }
reloadPlugin(idOrFileOrContent) { reloadPlugin(idOrFileOrAddon) {
const error = this.reloadContent(idOrFileOrContent); const error = this.reloadAddon(idOrFileOrAddon);
if (error) Modals.showContentErrors({plugins: [error]}); if (error) Modals.showAddonErrors({plugins: [error]});
return typeof(idOrFileOrContent) == "string" ? this.contentList.find(c => c.id == idOrFileOrContent || c.filename == idOrFileOrContent) : idOrFileOrContent; return typeof(idOrFileOrAddon) == "string" ? this.addonList.find(c => c.id == idOrFileOrAddon || c.filename == idOrFileOrAddon) : idOrFileOrAddon;
} }
/* Overrides */ /* Overrides */
initializeContent(content) { initializeAddon(addon) {
if (!content.type) return new ContentError(content.name, content.filename, "Plugin had no exports", {message: "Plugin had no exports or no name property.", stack: ""}); if (!addon.type) return new AddonError(addon.name, addon.filename, "Plugin had no exports", {message: "Plugin had no exports or no name property.", stack: ""});
try { try {
const thePlugin = new content.type(); const thePlugin = new addon.type();
content.plugin = thePlugin; addon.plugin = thePlugin;
content.name = thePlugin.getName() || content.name; addon.name = thePlugin.getName() || addon.name;
content.author = thePlugin.getAuthor() || content.author || "No author"; addon.author = thePlugin.getAuthor() || addon.author || "No author";
content.description = thePlugin.getDescription() || content.description || "No description"; addon.description = thePlugin.getDescription() || addon.description || "No description";
content.version = thePlugin.getVersion() || content.version || "No version"; addon.version = thePlugin.getVersion() || addon.version || "No version";
try { try {
if (typeof(content.plugin.load) == "function") content.plugin.load(); if (typeof(addon.plugin.load) == "function") addon.plugin.load();
} }
catch (error) { catch (error) {
this.state[content.id] = false; this.state[addon.id] = false;
return new ContentError(content.name, content.filename, "load() could not be fired.", {message: error.message, stack: error.stack}); return new AddonError(addon.name, addon.filename, "load() could not be fired.", {message: error.message, stack: error.stack});
} }
} }
catch (error) {return new ContentError(content.name, content.filename, "Could not be constructed.", {message: error.message, stack: error.stack});} catch (error) {return new AddonError(addon.name, addon.filename, "Could not be constructed.", {message: error.message, stack: error.stack});}
} }
getContentModification(module, content, meta) { getFileModification(module, fileContent, meta) {
module._compile(content, module.filename); module._compile(fileContent, module.filename);
const didExport = !Utilities.isEmpty(module.exports); const didExport = !Utilities.isEmpty(module.exports);
if (didExport) { if (didExport) {
meta.type = module.exports; meta.type = module.exports;
module.exports = meta; module.exports = meta;
return ""; return "";
} }
content += `\nmodule.exports = ${JSON.stringify(meta)};\nmodule.exports.type = ${meta.exports || meta.name};`; fileContent += `\nmodule.exports = ${JSON.stringify(meta)};\nmodule.exports.type = ${meta.exports || meta.name};`;
return content; return fileContent;
} }
startContent(id) {return this.startPlugin(id);} startAddon(id) {return this.startPlugin(id);}
stopContent(id) {return this.stopPlugin(id);} stopAddon(id) {return this.stopPlugin(id);}
startPlugin(idOrContent) { startPlugin(idOrAddon) {
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent; const addon = typeof(idOrAddon) == "string" ? this.addonList.find(p => p.id == idOrAddon) : idOrAddon;
if (!content) return; if (!addon) return;
const plugin = content.plugin; const plugin = addon.plugin;
try { try {
plugin.start(); plugin.start();
this.emit("started", content.id); this.emit("started", addon.id);
Toasts.show(`${content.name} v${content.version} has started.`); Toasts.show(`${addon.name} v${addon.version} has started.`);
} }
catch (err) { catch (err) {
this.state[content.id] = false; this.state[addon.id] = false;
Toasts.error(`${content.name} v${content.version} could not be started.`); Toasts.error(`${addon.name} v${addon.version} could not be started.`);
Logger.stacktrace(this.name, content.name + " could not be started.", err); Logger.stacktrace(this.name, addon.name + " could not be started.", err);
return new ContentError(content.name, content.filename, "start() could not be fired.", {message: err.message, stack: err.stack}); return new AddonError(addon.name, addon.filename, "start() could not be fired.", {message: err.message, stack: err.stack});
} }
} }
stopPlugin(idOrContent) { stopPlugin(idOrAddon) {
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent; const addon = typeof(idOrAddon) == "string" ? this.addonList.find(p => p.id == idOrAddon) : idOrAddon;
if (!content) return; if (!addon) return;
const plugin = content.plugin; const plugin = addon.plugin;
try { try {
plugin.stop(); plugin.stop();
this.emit("stopped", content.id); this.emit("stopped", addon.id);
Toasts.show(`${content.name} v${content.version} has stopped.`); Toasts.show(`${addon.name} v${addon.version} has stopped.`);
} }
catch (err) { catch (err) {
this.state[content.id] = false; this.state[addon.id] = false;
Toasts.error(`${content.name} v${content.version} could not be stopped.`); Toasts.error(`${addon.name} v${addon.version} could not be stopped.`);
Logger.stacktrace(this.name, content.name + " could not be stopped.", err); Logger.stacktrace(this.name, addon.name + " could not be stopped.", err);
return new ContentError(content.name, content.filename, "stop() could not be fired.", {message: err.message, stack: err.stack}); return new AddonError(addon.name, addon.filename, "stop() could not be fired.", {message: err.message, stack: err.stack});
} }
} }
@ -143,23 +143,23 @@ export default new class PluginManager extends ContentManager {
onSwitch() { onSwitch() {
this.emit("page-switch"); this.emit("page-switch");
for (let i = 0; i < this.contentList.length; i++) { for (let i = 0; i < this.addonList.length; i++) {
const plugin = this.contentList[i].plugin; const plugin = this.addonList[i].plugin;
if (!this.state[this.contentList[i].id]) continue; if (!this.state[this.addonList[i].id]) continue;
if (typeof(plugin.onSwitch) === "function") { if (typeof(plugin.onSwitch) === "function") {
try { plugin.onSwitch(); } try { plugin.onSwitch(); }
catch (err) { Logger.stacktrace(this.name, "Unable to fire onSwitch for " + this.contentList[i].name + ".", err); } catch (err) { Logger.stacktrace(this.name, "Unable to fire onSwitch for " + this.addonList[i].name + ".", err); }
} }
} }
} }
onMutation(mutation) { onMutation(mutation) {
for (let i = 0; i < this.contentList.length; i++) { for (let i = 0; i < this.addonList.length; i++) {
const plugin = this.contentList[i].plugin; const plugin = this.addonList[i].plugin;
if (!this.state[this.contentList[i].id]) continue; if (!this.state[this.addonList[i].id]) continue;
if (typeof plugin.observer === "function") { if (typeof plugin.observer === "function") {
try { plugin.observer(mutation); } try { plugin.observer(mutation); }
catch (err) { Logger.stacktrace(this.name, "Unable to fire observer for " + this.contentList[i].name + ".", err); } catch (err) { Logger.stacktrace(this.name, "Unable to fire observer for " + this.addonList[i].name + ".", err); }
} }
} }
} }

View File

@ -1,5 +1,5 @@
import {Config} from "data"; import {Config} from "data";
import ContentManager from "./contentmanager"; import AddonManager from "./addonmanager";
import Settings from "./settingsmanager"; import Settings from "./settingsmanager";
import DOMManager from "./dommanager"; import DOMManager from "./dommanager";
import Strings from "./strings"; import Strings from "./strings";
@ -10,17 +10,17 @@ import SettingsRenderer from "../ui/settings";
const path = require("path"); const path = require("path");
export default new class ThemeManager extends ContentManager { export default new class ThemeManager extends AddonManager {
get name() {return "ThemeManager";} get name() {return "ThemeManager";}
get moduleExtension() {return ".css";} get moduleExtension() {return ".css";}
get extension() {return ".theme.css";} get extension() {return ".theme.css";}
get contentFolder() {return path.resolve(Config.dataPath, "themes");} get addonFolder() {return path.resolve(Config.dataPath, "themes");}
get prefix() {return "theme";} get prefix() {return "theme";}
initialize() { initialize() {
const errors = super.initialize(); const errors = super.initialize();
Settings.registerPanel("themes", Strings.Panels.themes, {element: () => SettingsRenderer.getContentPanel(Strings.Panels.themes, this.contentList, this.state, { Settings.registerPanel("themes", Strings.Panels.themes, {element: () => SettingsRenderer.getAddonPanel(Strings.Panels.themes, this.addonList, this.state, {
folder: this.contentFolder, folder: this.addonFolder,
onChange: this.toggleTheme.bind(this), onChange: this.toggleTheme.bind(this),
reload: this.reloadTheme.bind(this), reload: this.reloadTheme.bind(this),
refreshList: this.updateThemeList.bind(this) refreshList: this.updateThemeList.bind(this)
@ -30,44 +30,44 @@ export default new class ThemeManager extends ContentManager {
/* Aliases */ /* Aliases */
updateThemeList() {return this.updateList();} updateThemeList() {return this.updateList();}
loadAllThemes() {return this.loadAllContent();} loadAllThemes() {return this.loadAllAddons();}
enableTheme(idOrContent) {return this.enableContent(idOrContent);} enableTheme(idOrAddon) {return this.enableAddon(idOrAddon);}
disableTheme(idOrContent) {return this.disableContent(idOrContent);} disableTheme(idOrAddon) {return this.disableAddon(idOrAddon);}
toggleTheme(id) {return this.toggleContent(id);} toggleTheme(id) {return this.toggleAddon(id);}
unloadTheme(idOrFileOrContent) {return this.unloadContent(idOrFileOrContent);} unloadTheme(idOrFileOrAddon) {return this.unloadAddon(idOrFileOrAddon);}
loadTheme(filename) { loadTheme(filename) {
const error = this.loadContent(filename); const error = this.loadAddon(filename);
if (error) Modals.showContentErrors({themes: [error]}); if (error) Modals.showAddonErrors({themes: [error]});
} }
reloadTheme(idOrFileOrContent) { reloadTheme(idOrFileOrAddon) {
const error = this.reloadContent(idOrFileOrContent); const error = this.reloadAddon(idOrFileOrAddon);
if (error) Modals.showContentErrors({themes: [error]}); if (error) Modals.showAddonErrors({themes: [error]});
} }
/* Overrides */ /* Overrides */
getContentModification(module, content, meta) { getFileModification(module, fileContent, meta) {
meta.css = content; meta.css = fileContent;
return `module.exports = ${JSON.stringify(meta)};`; return `module.exports = ${JSON.stringify(meta)};`;
} }
startContent(id) {return this.addTheme(id);} startAddon(id) {return this.addTheme(id);}
stopContent(id) {return this.removeTheme(id);} stopAddon(id) {return this.removeTheme(id);}
addTheme(idOrContent) { addTheme(idOrAddon) {
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent; const addon = typeof(idOrAddon) == "string" ? this.addonList.find(p => p.id == idOrAddon) : idOrAddon;
if (!content) return; if (!addon) return;
DOMManager.injectTheme(content.id, content.css); DOMManager.injectTheme(addon.id, addon.css);
Toasts.show(`${content.name} v${content.version} has been applied.`); Toasts.show(`${addon.name} v${addon.version} has been applied.`);
} }
removeTheme(idOrContent) { removeTheme(idOrAddon) {
const content = typeof(idOrContent) == "string" ? this.contentList.find(p => p.id == idOrContent) : idOrContent; const addon = typeof(idOrAddon) == "string" ? this.addonList.find(p => p.id == idOrAddon) : idOrAddon;
if (!content) return; if (!addon) return;
DOMManager.removeTheme(content.id); DOMManager.removeTheme(addon.id);
Toasts.show(`${content.name} v${content.version} has been removed.`); Toasts.show(`${addon.name} v${addon.version} has been removed.`);
} }
}; };

View File

@ -1,4 +1,4 @@
export default class ContentError extends Error { export default class AddonError extends Error {
constructor(name, filename, message, error) { constructor(name, filename, message, error) {
super(message); super(message);
this.name = name; this.name = name;

View File

@ -2,7 +2,7 @@ import {Logger, WebpackModules, Utilities, React, Settings, Strings} from "modul
export default class Modals { export default class Modals {
static get shouldShowContentErrors() {return Settings.get("settings", "addons", "addonErrors");} static get shouldShowAddonErrors() {return Settings.get("settings", "addons", "addonErrors");}
static get ModalStack() {return WebpackModules.getByProps("push", "update", "pop", "popWithKey");} static get ModalStack() {return WebpackModules.getByProps("push", "update", "pop", "popWithKey");}
static get AlertModal() {return WebpackModules.getByPrototypes("handleCancel", "handleSubmit", "handleMinorConfirm");} static get AlertModal() {return WebpackModules.getByPrototypes("handleCancel", "handleSubmit", "handleMinorConfirm");}
@ -90,8 +90,8 @@ export default class Modals {
}); });
} }
static showContentErrors({plugins: pluginErrors = [], themes: themeErrors = []}) { static showAddonErrors({plugins: pluginErrors = [], themes: themeErrors = []}) {
if (!pluginErrors || !themeErrors || !this.shouldShowContentErrors) return; if (!pluginErrors || !themeErrors || !this.shouldShowAddonErrors) return;
if (!pluginErrors.length && !themeErrors.length) return; if (!pluginErrors.length && !themeErrors.length) return;
const backdrop = WebpackModules.getByProps("backdrop") || {backdrop: "backdrop-1wrmKb"}; const backdrop = WebpackModules.getByProps("backdrop") || {backdrop: "backdrop-1wrmKb"};
const baseModalClasses = WebpackModules.getModule(m => m.modal && m.inner && !m.sizeMedium) || {modal: "modal-36zFtW", inner: "inner-2VEzy9"}; const baseModalClasses = WebpackModules.getModule(m => m.modal && m.inner && !m.sizeMedium) || {modal: "modal-36zFtW", inner: "inner-2VEzy9"};
@ -138,7 +138,7 @@ export default class Modals {
if (err.error) { if (err.error) {
error.find("a").on("click", (e) => { error.find("a").on("click", (e) => {
e.preventDefault(); e.preventDefault();
Logger.stacktrace("ContentError", `Error details for ${err.name ? err.name : err.file}.`, err.error); Logger.stacktrace("AddonError", `Error details for ${err.name ? err.name : err.file}.`, err.error);
}); });
} }
} }

View File

@ -1,6 +1,6 @@
import {React, WebpackModules, Patcher, ReactComponents, Utilities, Settings, Events} from "modules"; import {React, WebpackModules, Patcher, ReactComponents, Utilities, Settings, Events} from "modules";
import ContentList from "./settings/contentlist"; import AddonList from "./settings/addonlist";
import SettingsGroup from "./settings/group"; import SettingsGroup from "./settings/group";
import SettingsTitle from "./settings/title"; import SettingsTitle from "./settings/title";
import Attribution from "./settings/attribution"; import Attribution from "./settings/attribution";
@ -34,11 +34,11 @@ export default new class SettingsRenderer {
})]; })];
} }
getContentPanel(title, contentList, contentState, options = {}) { getAddonPanel(title, addonList, addonState, options = {}) {
return React.createElement(ContentList, Object.assign({}, { return React.createElement(AddonList, Object.assign({}, {
title: title, title: title,
contentList: contentList, addonList: addonList,
contentState: contentState addonState: addonState
}, options)); }, options));
} }

View File

@ -0,0 +1,134 @@
import {React, Logger, Strings} from "modules";
import CloseButton from "../icons/close";
import ReloadIcon from "../icons/reload";
export default class PluginCard extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.showSettings = this.showSettings.bind(this);
this.state = {
checked: this.props.enabled,
settingsOpen: false
};
this.hasSettings = typeof this.props.addon.plugin.getSettingsPanel === "function";
this.settingsPanel = "";
this.panelRef = React.createRef();
this.reload = this.reload.bind(this);
// this.onReload = this.onReload.bind(this);
this.closeSettings = this.closeSettings.bind(this);
}
reload() {
if (!this.props.reload) return;
this.props.addon = this.props.reload(this.props.addon.id);
this.forceUpdate();
}
componentDidUpdate() {
if (!this.state.settingsOpen) return;
if (this.settingsPanel instanceof Node) this.panelRef.current.appendChild(this.settingsPanel);
// if (!SettingsCookie["fork-ps-3"]) return;
const isHidden = (container, element) => {
const cTop = container.scrollTop;
const cBottom = cTop + container.clientHeight;
const eTop = element.offsetTop;
const eBottom = eTop + element.clientHeight;
return (eTop < cTop || eBottom > cBottom);
};
const panel = $(this.panelRef.current);
const container = panel.parents(".scroller-2FKFPG");
if (!isHidden(container[0], panel[0])) return;
container.animate({
scrollTop: panel.offset().top - container.offset().top + container.scrollTop() - 30
}, 300);
}
getString(value) {return typeof value == "string" ? value : value.toString();}
closeSettings() {
this.panelRef.current.innerHTML = "";
this.setState({settingsOpen: false});
}
buildTitle(name, version, author) {
const title = Strings.Addons.title.split(/({{[A-Za-z]+}})/);
const nameIndex = title.findIndex(s => s == "{{name}}");
if (nameIndex) title[nameIndex] = React.createElement("span", {className: "bd-name"}, name);
const versionIndex = title.findIndex(s => s == "{{version}}");
if (nameIndex) title[versionIndex] = React.createElement("span", {className: "bd-version"}, version);
const authorIndex = title.findIndex(s => s == "{{author}}");
if (nameIndex) title[authorIndex] = React.createElement("span", {className: "bd-author"}, author);
return title.flat();
}
get settingsComponent() {
const addon = this.props.addon;
const name = this.getString(addon.name);
try { this.settingsPanel = addon.plugin.getSettingsPanel(); }
catch (err) { Logger.stacktrace("Plugin Settings", "Unable to get settings panel for " + name + ".", err); }
const props = {id: `plugin-settings-${name}`, className: "plugin-settings", ref: this.panelRef};
if (typeof(settingsPanel) == "string") props.dangerouslySetInnerHTML = this.settingsPanel;
return <li className="settings-open bd-switch-item">
<div className="bd-close" onClick={this.closeSettings}><CloseButton /></div>
<div {...props}>{this.settingsPanel instanceof React.Component ? this.settingsPanel : null}</div>
</li>;
}
buildLink(which) {
const url = this.props.addon[which];
if (!url) return null;
return <a className="bd-link bd-link-website" href={url} target="_blank" rel="noopener noreferrer">{Strings.Addons[which]}</a>;
}
get footer() {
const links = ["website", "source"];
if (!links.some(l => this.props.addon[l]) && !this.hasSettings) return null;
const linkComponents = links.map(this.buildLink.bind(this)).filter(c => c);
return <div className="bd-footer">
<span className="bd-links">{linkComponents.map((comp, i) => i < linkComponents.length - 1 ? [comp, " | "] : [comp]).flat()}</span>
{this.hasSettings && <button onClick={this.showSettings} className="bd-button bd-button-plugin-settings" disabled={!this.state.checked}>{Strings.Addons.pluginSettings}</button>}
</div>;
}
render() {
if (this.state.settingsOpen) return this.settingsComponent;
const {addon} = this.props;
const name = this.getString(addon.name);
const author = this.getString(addon.author);
const description = this.getString(addon.description);
const version = this.getString(addon.version);
return <li dataName={name} dataVersion={version} className="settings-closed bd-switch-item">
<div className="bd-header">
<span className="bd-header-title">{this.buildTitle(name, version, author)}</span>
<div className="bd-controls">
{this.props.showReloadIcon && <ReloadIcon className="bd-reload bd-reload-card" onClick={this.reload} />}
<label className="bd-switch-wrapper bd-flex-child">
<input className="bd-switch-checkbox" checked={this.state.checked} onChange={this.onChange} type="checkbox" />
<div className={this.state.checked ? "bd-switch checked" : "bd-switch"} />
</label>
</div>
</div>
<div className="bd-description-wrap scroller-wrap fade"><div className="bd-description scroller">{description}</div></div>
{this.footer}
</li>;
}
onChange() {
this.setState({checked: !this.state.checked});
this.props.onChange && this.props.onChange(this.props.addon.id);
}
showSettings() {
if (!this.hasSettings) return;
this.setState({settingsOpen: true});
}
}

View File

@ -5,7 +5,7 @@ import PluginCard from "./plugincard";
import ThemeCard from "./themecard"; import ThemeCard from "./themecard";
import ReloadIcon from "../icons/reload"; import ReloadIcon from "../icons/reload";
export default class ContentList extends React.Component { export default class AddonList extends React.Component {
reload() { reload() {
if (this.props.refreshList) this.props.refreshList(); if (this.props.refreshList) this.props.refreshList();
@ -13,15 +13,15 @@ export default class ContentList extends React.Component {
} }
render() { render() {
const {title, folder, contentList, contentState, onChange, reload} = this.props; const {title, folder, addonList, addonState, onChange, reload} = this.props;
const showReloadIcon = !Settings.get("settings", "addons", "autoReload"); const showReloadIcon = !Settings.get("settings", "addons", "autoReload");
const button = folder ? {title: Strings.Addons.openFolder.format({type: title}), onClick: () => {require("electron").shell.openItem(folder);}} : null; const button = folder ? {title: Strings.Addons.openFolder.format({type: title}), onClick: () => {require("electron").shell.openItem(folder);}} : null;
return [ return [
<SettingsTitle key="title" text={title} button={button} otherChildren={showReloadIcon && <ReloadIcon className="bd-reload" onClick={this.reload.bind(this)} />} />, <SettingsTitle key="title" text={title} button={button} otherChildren={showReloadIcon && <ReloadIcon className="bd-reload" onClick={this.reload.bind(this)} />} />,
<ul key="ContentList" className={"bda-slist"}> <ul key="addonList" className={"bd-slist"}>
{contentList.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())).map(content => { {addonList.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())).map(addon => {
const CardType = content.type ? PluginCard : ThemeCard; const CardType = addon.type ? PluginCard : ThemeCard;
return <CardType showReloadIcon={showReloadIcon} key={content.id} enabled={contentState[content.id]} content={content} onChange={onChange} reload={reload} />; return <CardType showReloadIcon={showReloadIcon} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} />;
})} })}
</ul> </ul>
]; ];

View File

@ -15,7 +15,7 @@ export default class BBDAttribution extends React.Component {
} }
render() { render() {
return <div className= "bd-version"> return <div id="bbd-version">
{this.buildTitle("BBD", Config.bbdVersion, <a href="https://github.com/rauenzi" target="_blank" rel="noopener noreferrer">Zerebos</a>)} {this.buildTitle("BBD", Config.bbdVersion, <a href="https://github.com/rauenzi" target="_blank" rel="noopener noreferrer">Zerebos</a>)}
</div>; </div>;
} }

View File

@ -12,7 +12,7 @@ export default class PluginCard extends React.Component {
checked: this.props.enabled, checked: this.props.enabled,
settingsOpen: false settingsOpen: false
}; };
this.hasSettings = typeof this.props.content.plugin.getSettingsPanel === "function"; this.hasSettings = typeof this.props.addon.plugin.getSettingsPanel === "function";
this.settingsPanel = ""; this.settingsPanel = "";
this.panelRef = React.createRef(); this.panelRef = React.createRef();
@ -23,7 +23,7 @@ export default class PluginCard extends React.Component {
reload() { reload() {
if (!this.props.reload) return; if (!this.props.reload) return;
this.props.content = this.props.reload(this.props.content.id); this.props.addon = this.props.reload(this.props.addon.id);
this.forceUpdate(); this.forceUpdate();
} }
@ -58,41 +58,41 @@ export default class PluginCard extends React.Component {
buildTitle(name, version, author) { buildTitle(name, version, author) {
const title = Strings.Addons.title.split(/({{[A-Za-z]+}})/); const title = Strings.Addons.title.split(/({{[A-Za-z]+}})/);
const nameIndex = title.findIndex(s => s == "{{name}}"); const nameIndex = title.findIndex(s => s == "{{name}}");
if (nameIndex) title[nameIndex] = React.createElement("span", {className: "bda-name"}, name); if (nameIndex) title[nameIndex] = React.createElement("span", {className: "bd-name"}, name);
const versionIndex = title.findIndex(s => s == "{{version}}"); const versionIndex = title.findIndex(s => s == "{{version}}");
if (nameIndex) title[versionIndex] = React.createElement("span", {className: "bda-version"}, version); if (nameIndex) title[versionIndex] = React.createElement("span", {className: "bd-version"}, version);
const authorIndex = title.findIndex(s => s == "{{author}}"); const authorIndex = title.findIndex(s => s == "{{author}}");
if (nameIndex) title[authorIndex] = React.createElement("span", {className: "bda-author"}, author); if (nameIndex) title[authorIndex] = React.createElement("span", {className: "bd-author"}, author);
return title.flat(); return title.flat();
} }
get settingsComponent() { get settingsComponent() {
const content = this.props.content; const addon = this.props.addon;
const name = this.getString(content.name); const name = this.getString(addon.name);
try { this.settingsPanel = content.plugin.getSettingsPanel(); } try { this.settingsPanel = addon.plugin.getSettingsPanel(); }
catch (err) { Logger.stacktrace("Plugin Settings", "Unable to get settings panel for " + name + ".", err); } catch (err) { Logger.stacktrace("Plugin Settings", "Unable to get settings panel for " + name + ".", err); }
const props = {id: `plugin-settings-${name}`, className: "plugin-settings", ref: this.panelRef}; const props = {id: `plugin-settings-${name}`, className: "plugin-settings", ref: this.panelRef};
if (typeof(settingsPanel) == "string") props.dangerouslySetInnerHTML = this.settingsPanel; if (typeof(settingsPanel) == "string") props.dangerouslySetInnerHTML = this.settingsPanel;
return <li className="settings-open ui-switch-item"> return <li className="settings-open bd-switch-item">
<div className="bd-close" onClick={this.closeSettings}><CloseButton /></div> <div className="bd-close" onClick={this.closeSettings}><CloseButton /></div>
<div {...props}>{this.settingsPanel instanceof React.Component ? this.settingsPanel : null}</div> <div {...props}>{this.settingsPanel instanceof React.Component ? this.settingsPanel : null}</div>
</li>; </li>;
} }
buildLink(which) { buildLink(which) {
const url = this.props.content[which]; const url = this.props.addon[which];
if (!url) return null; if (!url) return null;
return <a className="bda-link bda-link-website" href={url} target="_blank" rel="noopener noreferrer">{Strings.Addons[which]}</a>; return <a className="bd-link bd-link-website" href={url} target="_blank" rel="noopener noreferrer">{Strings.Addons[which]}</a>;
} }
get footer() { get footer() {
const links = ["website", "source"]; const links = ["website", "source"];
if (!links.some(l => this.props.content[l]) && !this.hasSettings) return null; if (!links.some(l => this.props.addon[l]) && !this.hasSettings) return null;
const linkComponents = links.map(this.buildLink.bind(this)).filter(c => c); const linkComponents = links.map(this.buildLink.bind(this)).filter(c => c);
return <div className="bda-footer"> return <div className="bd-footer">
<span className="bda-links">{linkComponents.map((comp, i) => i < linkComponents.length - 1 ? [comp, " | "] : [comp]).flat()}</span> <span className="bd-links">{linkComponents.map((comp, i) => i < linkComponents.length - 1 ? [comp, " | "] : [comp]).flat()}</span>
{this.hasSettings && <button onClick={this.showSettings} className="bd-button bd-button-plugin-settings" disabled={!this.state.checked}>{Strings.Addons.pluginSettings}</button>} {this.hasSettings && <button onClick={this.showSettings} className="bd-button bd-button-plugin-settings" disabled={!this.state.checked}>{Strings.Addons.pluginSettings}</button>}
</div>; </div>;
} }
@ -100,31 +100,31 @@ export default class PluginCard extends React.Component {
render() { render() {
if (this.state.settingsOpen) return this.settingsComponent; if (this.state.settingsOpen) return this.settingsComponent;
const {content} = this.props; const {addon} = this.props;
const name = this.getString(content.name); const name = this.getString(addon.name);
const author = this.getString(content.author); const author = this.getString(addon.author);
const description = this.getString(content.description); const description = this.getString(addon.description);
const version = this.getString(content.version); const version = this.getString(addon.version);
return <li dataName={name} dataVersion={version} className="settings-closed ui-switch-item"> return <li dataName={name} dataVersion={version} className="settings-closed bd-switch-item">
<div className="bda-header"> <div className="bd-header">
<span className="bda-header-title">{this.buildTitle(name, version, author)}</span> <span className="bd-header-title">{this.buildTitle(name, version, author)}</span>
<div className="bda-controls"> <div className="bd-controls">
{this.props.showReloadIcon && <ReloadIcon className="bd-reload bd-reload-card" onClick={this.reload} />} {this.props.showReloadIcon && <ReloadIcon className="bd-reload bd-reload-card" onClick={this.reload} />}
<label className="ui-switch-wrapper ui-flex-child"> <label className="bd-switch-wrapper bd-flex-child">
<input className="ui-switch-checkbox" checked={this.state.checked} onChange={this.onChange} type="checkbox" /> <input className="bd-switch-checkbox" checked={this.state.checked} onChange={this.onChange} type="checkbox" />
<div className={this.state.checked ? "ui-switch checked" : "ui-switch"} /> <div className={this.state.checked ? "bd-switch checked" : "bd-switch"} />
</label> </label>
</div> </div>
</div> </div>
<div className="bda-description-wrap scroller-wrap fade"><div className="bda-description scroller">{description}</div></div> <div className="bd-description-wrap scroller-wrap fade"><div className="bd-description scroller">{description}</div></div>
{this.footer} {this.footer}
</li>; </li>;
} }
onChange() { onChange() {
this.setState({checked: !this.state.checked}); this.setState({checked: !this.state.checked});
this.props.onChange && this.props.onChange(this.props.content.id); this.props.onChange && this.props.onChange(this.props.addon.id);
} }
showSettings() { showSettings() {

View File

@ -7,7 +7,7 @@ export default class ThemeCard extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
checked: this.props.enabled, //ThemeManager.isEnabled(this.props.content.id), checked: this.props.enabled,
reloads: 0 reloads: 0
}; };
this.onChange = this.onChange.bind(this); this.onChange = this.onChange.bind(this);
@ -16,51 +16,51 @@ export default class ThemeCard extends React.Component {
reload() { reload() {
if (!this.props.reload) return; if (!this.props.reload) return;
this.props.content = this.props.reload(this.props.content.id); this.props.addon = this.props.reload(this.props.addon.id);
this.forceUpdate(); this.forceUpdate();
} }
buildTitle(name, version, author) { buildTitle(name, version, author) {
const title = Strings.Addons.title.split(/({{[A-Za-z]+}})/); const title = Strings.Addons.title.split(/({{[A-Za-z]+}})/);
const nameIndex = title.findIndex(s => s == "{{name}}"); const nameIndex = title.findIndex(s => s == "{{name}}");
if (nameIndex) title[nameIndex] = React.createElement("span", {className: "bda-name"}, name); if (nameIndex) title[nameIndex] = React.createElement("span", {className: "bd-name"}, name);
const versionIndex = title.findIndex(s => s == "{{version}}"); const versionIndex = title.findIndex(s => s == "{{version}}");
if (nameIndex) title[versionIndex] = React.createElement("span", {className: "bda-version"}, version); if (nameIndex) title[versionIndex] = React.createElement("span", {className: "bd-version"}, version);
const authorIndex = title.findIndex(s => s == "{{author}}"); const authorIndex = title.findIndex(s => s == "{{author}}");
if (nameIndex) title[authorIndex] = React.createElement("span", {className: "bda-author"}, author); if (nameIndex) title[authorIndex] = React.createElement("span", {className: "bd-author"}, author);
return title.flat(); return title.flat();
} }
render() { render() {
const {content} = this.props; const {addon} = this.props;
const name = content.name; const name = addon.name;
const description = content.description; const description = addon.description;
const version = content.version; const version = addon.version;
const author = content.author; const author = addon.author;
const website = content.website; const website = addon.website;
const source = content.source; const source = addon.source;
return React.createElement("li", {"data-name": name, "data-version": version, "className": "settings-closed ui-switch-item"}, return React.createElement("li", {"data-name": name, "data-version": version, "className": "settings-closed bd-switch-item"},
React.createElement("div", {className: "bda-header"}, React.createElement("div", {className: "bd-header"},
React.createElement("span", {className: "bda-header-title"}, React.createElement("span", {className: "bd-header-title"},
this.buildTitle(name, version, author) this.buildTitle(name, version, author)
), ),
React.createElement("div", {className: "bda-controls"}, React.createElement("div", {className: "bd-controls"},
this.props.showReloadIcon && React.createElement(ReloadIcon, {className: "bd-reload bd-reload-card", onClick: this.reload}), this.props.showReloadIcon && React.createElement(ReloadIcon, {className: "bd-reload bd-reload-card", onClick: this.reload}),
React.createElement("label", {className: "ui-switch-wrapper ui-flex-child", style: {flex: "0 0 auto"}}, React.createElement("label", {className: "bd-switch-wrapper bd-flex-child", style: {flex: "0 0 auto"}},
React.createElement("input", {checked: this.state.checked, onChange: this.onChange, className: "ui-switch-checkbox", type: "checkbox"}), React.createElement("input", {checked: this.state.checked, onChange: this.onChange, className: "bd-switch-checkbox", type: "checkbox"}),
React.createElement("div", {className: this.state.checked ? "ui-switch checked" : "ui-switch"}) React.createElement("div", {className: this.state.checked ? "bd-switch checked" : "bd-switch"})
) )
) )
), ),
React.createElement("div", {className: "bda-description-wrap scroller-wrap fade"}, React.createElement("div", {className: "bd-description-wrap scroller-wrap fade"},
React.createElement("div", {className: "bda-description scroller"}, description) React.createElement("div", {className: "bd-description scroller"}, description)
), ),
(website || source) && React.createElement("div", {className: "bda-footer"}, (website || source) && React.createElement("div", {className: "bd-footer"},
React.createElement("span", {className: "bda-links"}, React.createElement("span", {className: "bd-links"},
website && React.createElement("a", {className: "bda-link", href: website, target: "_blank"}, "Website"), website && React.createElement("a", {className: "bd-link", href: website, target: "_blank"}, "Website"),
website && source && " | ", website && source && " | ",
source && React.createElement("a", {className: "bda-link", href: source, target: "_blank"}, "Source") source && React.createElement("a", {className: "bd-link", href: source, target: "_blank"}, "Source")
) )
) )
); );
@ -68,6 +68,6 @@ export default class ThemeCard extends React.Component {
onChange() { onChange() {
this.setState({checked: !this.state.checked}); this.setState({checked: !this.state.checked});
this.props.onChange && this.props.onChange(this.props.content.id); this.props.onChange && this.props.onChange(this.props.addon.id);
} }
} }