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);
}
.bd-version {
#bbd-version {
font-size: 12px;
font-weight: 600;
color: #72767d;
@ -539,7 +539,7 @@
border-radius: 0 0 5px 5px;
}
#bda-qem {
#bd-qem {
border-radius: 5px 5px 0 0;
background: #FFF;
border-bottom: 1px solid rgba(0, 0, 0, 0.1) !important;
@ -548,42 +548,42 @@
flex-direction: row;
padding-right: 1px !important;
}
#bda-qem button {
#bd-qem button {
border-left: 1px solid #EFEFEF;
background: transparent;
box-shadow: #CECECE 1px 0 0 0;
flex-grow: 1;
}
#bda-qem button:hover {
#bd-qem button:hover {
background: #ECECEC;
}
#bda-qem-twitch {
#bd-qem-twitch {
border-radius: 5px 0 0 0;
order: 2;
}
#bda-qem-emojis {
#bd-qem-emojis {
border-radius: 0 5px 0 0;
order: 3;
}
#bda-qem-favourite {
#bd-qem-favourite {
order: 3;
}
#bda-qem button.active {
#bd-qem button.active {
background-color: #E2E2E2;
}
#bda-qem-twitch-container, #bda-qem-favourite-container {
#bd-qem-twitch-container, #bd-qem-favourite-container {
width: 346px;
height: 329px;
background-color: #FFF;
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%;
}
@ -591,7 +591,7 @@
padding: 5px 0 0 15px;
}
.bda-qme-hidden #bda-qem-emojis {
.bd-qme-hidden #bd-qem-emojis {
display: none;
}
/* ================ */
@ -632,7 +632,7 @@
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;
}
@ -644,7 +644,7 @@
left: 0;
right: 0;
margin-top: 4px;
}
} */
.bd-server-card.bd-server-card-pinned {
margin-bottom: 15px;
@ -716,7 +716,7 @@
top: 0;
}
#pubslayer .ui-tab-bar-item {
/* #pubslayer .ui-tab-bar-item {
color: #b9bbbe;
padding-top: 6px;
padding-bottom: 6px;
@ -774,7 +774,7 @@
#pubslayer h5.ui-form-title {
color: #f6f6f7;
}
} */
#pubslayer button {
background: #7289da;
@ -1002,7 +1002,7 @@ body .ace_closeButton:active {
margin-left: 10px;
}
#bd-settings-sidebar .ui-tab-bar-item {
/* #bd-settings-sidebar .ui-tab-bar-item {
font-size: 16px;
font-weight: 500;
line-height: 20px;
@ -1087,27 +1087,27 @@ body .ace_closeButton:active {
}
.theme-light #bd-settingspane-container h2.ui-form-title {
color: #4f545c;
}
} */
.ui-switch-item {
.bd-switch-item {
flex-direction: column;
margin-top: 8px;
}
.ui-switch-item h3 {
.bd-switch-item h3 {
font-size: 16px;
font-weight: 500;
line-height: 24px;
flex: 1;
}
.theme-dark .ui-switch-item h3 {
.theme-dark .bd-switch-item h3 {
color: #f6f6f7;
}
.theme-light .ui-switch-item h3 {
.theme-light .bd-switch-item h3 {
color: #4f545c;
}
.ui-switch-item .style-description {
/* .ui-switch-item .style-description {
font-size: 14px;
font-weight: 500;
line-height: 20px;
@ -1120,9 +1120,9 @@ body .ace_closeButton:active {
}
.theme-light .ui-switch-item .style-description {
color: rgba(114,118,125,.6);
}
} */
.ui-switch-item .ui-switch-wrapper {
.bd-switch-item .bd-switch-wrapper {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
@ -1134,7 +1134,7 @@ body .ace_closeButton:active {
flex: 0 0 auto;
}
.ui-switch-item .ui-switch-wrapper input {
.bd-switch-item .bd-switch-wrapper input {
position: absolute;
opacity: 0;
cursor: pointer;
@ -1143,7 +1143,7 @@ body .ace_closeButton:active {
z-index: 1;
}
.ui-switch-item .ui-switch-wrapper .ui-switch {
.bd-switch-item .bd-switch-wrapper .bd-switch {
background: #7289da;
position: absolute;
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;
}
.ui-switch-item .ui-switch-wrapper .ui-switch:before {
.bd-switch-item .bd-switch-wrapper .bd-switch:before {
content: "";
display: block;
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);
}
.ui-switch-item .ui-switch-wrapper .ui-switch.checked {
.bd-switch-item .bd-switch-wrapper .bd-switch.checked {
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);
}
@ -1185,9 +1185,9 @@ body .ace_closeButton:active {
#bd-settingspane-container .scroller-wrap .scroller {
display: flex;
}
.content-column .ui-form-title:first-child {
/* .content-column .ui-form-title:first-child {
margin-top: 0;
}
} */
/* ================= */
/* END BD SETTINGS */
@ -1215,15 +1215,15 @@ body .ace_closeButton:active {
margin-right: 5px;
}
.bda-controls {
.bd-controls {
display: flex;
}
.bda-slist {
.bd-slist {
user-select: text;
}
.bda-slist li {
.bd-slist li {
max-height: 175px;
margin-bottom: 20px;
padding: 5px 8px;
@ -1231,23 +1231,23 @@ body .ace_closeButton:active {
border-radius: 5px;
overflow: hidden;
}
.theme-dark .bda-slist li {
.theme-dark .bd-slist li {
background-color: rgba(32,34,37,.6);
color: #f6f6f7;
border-color: #202225;
}
.theme-light .bda-slist li {
.theme-light .bd-slist li {
background-color: #f8f9f9;
color: #4f545c;
border-color: #dcddde;
}
.bda-slist li.settings-open {
.bd-slist li.settings-open {
max-height: 800px;
overflow-y: auto;
}
.bda-slist .bda-header {
.bd-slist .bd-header {
font-size: 12px;
font-weight: 700;
display: flex;
@ -1257,36 +1257,36 @@ body .ace_closeButton:active {
border-bottom: 1px solid transparent;
overflow: hidden;
}
.theme-dark .bda-slist .bda-header {
.theme-dark .bd-slist .bd-header {
color: #f6f6f7;
border-bottom-color: rgba(114,118,125,.3);
}
.theme-light .bda-slist .bda-header {
.theme-light .bd-slist .bd-header {
color: #4f545c;
border-bottom-color: rgba(185,187,190,.3);
}
.bda-slist .bda-description {
.bd-slist .bd-description {
word-break: break-word;
max-height: 100px;
margin: 5px 0;
padding: 5px 0;
overflow-y: auto;
}
.theme-dark .bda-slist .bda-description {
.theme-dark .bd-slist .bd-description {
color: #b9bbbe;
}
.theme-light .bda-slist .bda-description {
.theme-light .bd-slist .bd-description {
color: #72767d;
}
.bda-slist .scroller::-webkit-scrollbar-track-piece,
.bda-slist .scroller::-webkit-scrollbar-thumb {
.bd-slist .scroller::-webkit-scrollbar-track-piece,
.bd-slist .scroller::-webkit-scrollbar-thumb {
border-radius:0 !important;
border-color:transparent;
}
.bda-slist .bda-footer {
.bd-slist .bd-footer {
font-size: 12px;
font-weight: 700;
display: flex;
@ -1296,14 +1296,14 @@ body .ace_closeButton:active {
border-top: 1px solid transparent;
overflow: hidden;
}
.theme-dark .bda-slist .bda-footer {
.theme-dark .bd-slist .bd-footer {
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);
}
.bda-slist .bda-footer button {
.bd-slist .bd-footer button {
background: #7289da;
color: #FFF;
border-radius: 5px;
@ -1313,15 +1313,15 @@ body .ace_closeButton:active {
transition: opacity 250ms ease;
}
.bda-slist .bda-footer button:disabled {
.bd-slist .bd-footer button:disabled {
opacity: 0.4;
}
.bda-slist .bda-footer a {
.bd-slist .bd-footer a {
color: #7289da;
}
.bda-slist .bda-footer a:hover {
.bd-slist .bd-footer a:hover {
text-decoration: underline;
}
/* ======================= */
@ -1914,101 +1914,101 @@ body .ace_closeButton:active {
/* BEGIN DARK MODE */
/* =============== */
/* Emoji Picker */
.bda-dark #bda-qem-favourite-container,
.bda-dark #bda-qem-twitch-container {
.bd-dark #bd-qem-favourite-container,
.bd-dark #bd-qem-twitch-container {
background-color: #353535;
}
.bda-dark #bda-qem {
.bd-dark #bd-qem {
border-bottom: 1px solid #464646 !important;
background: #353535;
}
.bda-dark #bda-qem button {
.bd-dark #bd-qem button {
background: #353535;
border-left: 1px solid #242424;
box-shadow: #424242 1px 0 0 0;
color: #FFF;
}
.bda-dark #bda-qem button.active {
.bd-dark #bd-qem button.active {
background-color: #292929;
}
.bda-dark #bda-qem button:hover {
.bd-dark #bd-qem button:hover {
background-color: #303030;
}
.bda-dark #bda-qem-favourite-container,
.bda-dark #bda-qem-twitch-container {
.bd-dark #bd-qem-favourite-container,
.bd-dark #bd-qem-twitch-container {
background-color: #353535;
}
.bda-dark .emojiPicker-3m1S-j {
.bd-dark .emojiPicker-3m1S-j {
background-color: #353535;
}
.bda-dark .emojiPicker-3m1S-j .category-2U57w6 {
.bd-dark .emojiPicker-3m1S-j .category-2U57w6 {
background-color: #353535;
}
.bda-dark .emojiPicker-3m1S-j .header-1nkwgG .searchBar-2pWH0_ {
.bd-dark .emojiPicker-3m1S-j .header-1nkwgG .searchBar-2pWH0_ {
background-color: #2B2B2B;
}
.bda-dark .emojiPicker-3m1S-j .searchBar-2pWH0_ input {
.bd-dark .emojiPicker-3m1S-j .searchBar-2pWH0_ input {
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;
}
.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);
}
.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);
}
.bda-dark .emojiPicker-3m1S-j .diversitySelector-tmmMv0 .popout-2nUePc {
.bd-dark .emojiPicker-3m1S-j .diversitySelector-tmmMv0 .popout-2nUePc {
background: #353535;
border-color: #202020;
}
.bda-dark #bda-qem-favourite-container .scroller::-webkit-scrollbar,
.bda-dark #bda-qem-favourite-container .scroller::-webkit-scrollbar-track,
.bda-dark #bda-qem-favourite-container .scroller::-webkit-scrollbar-track-piece,
.bda-dark #bda-qem-twitch-container .scroller::-webkit-scrollbar,
.bda-dark #bda-qem-twitch-container .scroller::-webkit-scrollbar-track,
.bda-dark #bda-qem-twitch-container .scroller::-webkit-scrollbar-track-piece,
.bda-dark .emojiPicker-3m1S-j .scroller-3vODG7::-webkit-scrollbar,
.bda-dark .emojiPicker-3m1S-j .scroller-3vODG7::-webkit-scrollbar-track,
.bda-dark .emojiPicker-3m1S-j .scroller-3vODG7::-webkit-scrollbar-track-piece {
.bd-dark #bd-qem-favourite-container .scroller::-webkit-scrollbar,
.bd-dark #bd-qem-favourite-container .scroller::-webkit-scrollbar-track,
.bd-dark #bd-qem-favourite-container .scroller::-webkit-scrollbar-track-piece,
.bd-dark #bd-qem-twitch-container .scroller::-webkit-scrollbar,
.bd-dark #bd-qem-twitch-container .scroller::-webkit-scrollbar-track,
.bd-dark #bd-qem-twitch-container .scroller::-webkit-scrollbar-track-piece,
.bd-dark .emojiPicker-3m1S-j .scroller-3vODG7::-webkit-scrollbar,
.bd-dark .emojiPicker-3m1S-j .scroller-3vODG7::-webkit-scrollbar-track,
.bd-dark .emojiPicker-3m1S-j .scroller-3vODG7::-webkit-scrollbar-track-piece {
background-color: #303030 !important;
border-color: #303030 !important;
}
.bda-dark #bda-qem-twitch-container .scroller::-webkit-scrollbar-thumb,
.bda-dark #bda-qem-favourite-container .scroller::-webkit-scrollbar-thumb,
.bda-dark .emojiPicker-3g68GS .scroller-3vODG7::-webkit-scrollbar-thumb {
.bd-dark #bd-qem-twitch-container .scroller::-webkit-scrollbar-thumb,
.bd-dark #bd-qem-favourite-container .scroller::-webkit-scrollbar-thumb,
.bd-dark .emojiPicker-3g68GS .scroller-3vODG7::-webkit-scrollbar-thumb {
border-color: #202020 !important;
background-color: #202020 !important;
}
/* add/create server */
.bda-dark .theme-light .slide-2pHaq5 {
.bd-dark .theme-light .slide-2pHaq5 {
background: #36393f;
}
.bda-dark .theme-dark .action-1lSjCi,
.bda-dark .theme-light .action-1lSjCi {
.bd-dark .theme-dark .action-1lSjCi,
.bd-dark .theme-light .action-1lSjCi {
background: #2F3136;
}
/* centered or */
.bda-dark .theme-dark .or-3THJsp,
.bda-dark .theme-light .or-3THJsp {
.bd-dark .theme-dark .or-3THJsp,
.bd-dark .theme-light .or-3THJsp {
background: #2F3136;
order: 2;
height: 56px;
@ -2020,48 +2020,48 @@ body .ace_closeButton:active {
border: 2px solid #484B52;
}
.bda-dark .create-3jownz {
.bd-dark .create-3jownz {
order: 1;
}
.bda-dark .join-33Tr-7 {
.bd-dark .join-33Tr-7 {
order: 3;
}
.bda-dark .theme-dark .actionIcon-2IISM_,
.bda-dark .theme-light .actionIcon-2IISM_ {
.bd-dark .theme-dark .actionIcon-2IISM_,
.bd-dark .theme-light .actionIcon-2IISM_ {
filter: grayscale(100%) brightness(60%);
}
.bda-dark .theme-light .footer-2yfCgX {
.bd-dark .theme-light .footer-2yfCgX {
background: #2F3136;
}
/* Region Select */
.bda-dark .regionSelectModal-12e-57 {
.bd-dark .regionSelectModal-12e-57 {
background: #36393f;
}
.bda-dark .regionSelectModal-12e-57 .regionSelectModalOption-2DSIZ3 {
.bd-dark .regionSelectModal-12e-57 .regionSelectModalOption-2DSIZ3 {
background: #2F3136;
border: 2px solid #484B52;
}
/* Ace Editor Settings */
.bda-dark ~ div #ace_settingsmenu {
.bd-dark ~ div #ace_settingsmenu {
color: #f6f6f7;
background: #36393f;
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,
.bda-dark ~ div #ace_settingsmenu input[type="text"] {
.bd-dark ~ div #ace_settingsmenu select,
.bd-dark ~ div #ace_settingsmenu input[type="text"] {
color: #f6f6f7;
background: #2F3136;
border: 1px solid #484B52;
}
.bda-dark ~ div .ace_closeButton::before {
.bd-dark ~ div .ace_closeButton::before {
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";}
enabled() {
document.getElementById("app-mount").classList.add("bda-dark", "bda-dark");
document.getElementById("app-mount").classList.add("bda-dark", "bd-dark");
}
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";
const headerHTML = `<div id="bda-qem">
<button class="active" id="bda-qem-twitch">Twitch</button>
<button id="bda-qem-favourite">Favourite</button>
<button id="bda-qem-emojis">Emojis</buttond>
const headerHTML = `<div id="bd-qem">
<button class="active" id="bd-qem-twitch">Twitch</button>
<button id="bd-qem-favourite">Favourite</button>
<button id="bd-qem-emojis">Emojis</buttond>
</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 scroller-2FKFPG">
<div class="emote-menu-inner">
@ -19,7 +19,7 @@ const twitchEmoteHTML = `<div id="bda-qem-twitch-container">
</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 scroller-2FKFPG">
<div class="emote-menu-inner">
@ -49,7 +49,7 @@ export default new class EmoteMenu extends Builtin {
constructor() {
super();
this.lastTab = "bda-qem-emojis";
this.lastTab = "bd-qem-emojis";
this.qmeHeader = Utilities.parseHTML(headerHTML);
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() {
const picker = document.querySelector(".emojiPicker-3m1S-j");
if (picker) picker.classList.add("bda-qme-hidden");
if (picker) picker.classList.add("bd-qme-hidden");
}
disableHideEmojis() {
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) {
@ -105,8 +105,8 @@ export default new class EmoteMenu extends Builtin {
const em = e.target.closest(".emote-container").children[0];
const menu = $(`<div id="removemenu" class="bd-context-menu context-menu theme-dark">Remove</div>`);
menu.css({
top: e.pageY - $("#bda-qem-favourite-container").offset().top,
left: e.pageX - $("#bda-qem-favourite-container").offset().left
top: e.pageY - $("#bd-qem-favourite-container").offset().top,
left: e.pageX - $("#bd-qem-favourite-container").offset().left
});
$(em).parent().append(menu);
menu.on("click", (event) => {
@ -126,28 +126,28 @@ export default new class EmoteMenu extends Builtin {
switchMenu(e) {
let id = typeof(e) == "string" ? e : e.target.id;
if (id == "bda-qem-emojis" && this.hideEmojis) id = "bda-qem-favourite";
const twitch = $("#bda-qem-twitch");
const fav = $("#bda-qem-favourite");
const emojis = $("#bda-qem-emojis");
if (id == "bd-qem-emojis" && this.hideEmojis) id = "bd-qem-favourite";
const twitch = $("#bd-qem-twitch");
const fav = $("#bd-qem-favourite");
const emojis = $("#bd-qem-emojis");
twitch.removeClass("active");
fav.removeClass("active");
emojis.removeClass("active");
$(".emojiPicker-3m1S-j").hide();
$("#bda-qem-favourite-container").hide();
$("#bda-qem-twitch-container").hide();
$("#bd-qem-favourite-container").hide();
$("#bd-qem-twitch-container").hide();
switch (id) {
case "bda-qem-twitch":
case "bd-qem-twitch":
twitch.addClass("active");
$("#bda-qem-twitch-container").show();
$("#bd-qem-twitch-container").show();
break;
case "bda-qem-favourite":
case "bd-qem-favourite":
fav.addClass("active");
$("#bda-qem-favourite-container").show();
$("#bd-qem-favourite-container").show();
break;
case "bda-qem-emojis":
case "bd-qem-emojis":
emojis.addClass("active");
$(".emojiPicker-3m1S-j").show();
$(".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;
const e = $(node);
if (this.hideEmojis) e.addClass("bda-qme-hidden");
else e.removeClass("bda-qme-hidden");
if (this.hideEmojis) e.addClass("bd-qme-hidden");
else e.removeClass("bd-qme-hidden");
e.prepend(this.qmeHeader);
e.append(this.teContainer);

View File

@ -61,7 +61,7 @@ function patchModuleLoad() {
const load = Module._load;
// const resolveFilename = Module._resolveFilename;
Module._load = function (request) {
Module._load = function(request) {
if (request === namespace || request.startsWith(prefix)) {
const requested = request.substr(prefix.length);
if (requested == "api") return BdApi;
@ -86,13 +86,5 @@ function 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 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();
await LocaleManager.initialize();
Logger.log("Startup", "Initializing Settings");
Settings.initialize();
@ -62,7 +62,7 @@ Core.prototype.init = async function() {
// Show loading errors
Logger.log("Startup", "Collecting Startup Errors");
Modals.showContentErrors({plugins: pluginErrors, themes: themeErrors});
Modals.showAddonErrors({plugins: pluginErrors, themes: themeErrors});
};
Core.prototype.waitForGuilds = function() {

View File

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

View File

@ -2,7 +2,7 @@ import {Logger, WebpackModules, Utilities, React, Settings, Strings} from "modul
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 AlertModal() {return WebpackModules.getByPrototypes("handleCancel", "handleSubmit", "handleMinorConfirm");}
@ -90,8 +90,8 @@ export default class Modals {
});
}
static showContentErrors({plugins: pluginErrors = [], themes: themeErrors = []}) {
if (!pluginErrors || !themeErrors || !this.shouldShowContentErrors) return;
static showAddonErrors({plugins: pluginErrors = [], themes: themeErrors = []}) {
if (!pluginErrors || !themeErrors || !this.shouldShowAddonErrors) return;
if (!pluginErrors.length && !themeErrors.length) return;
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"};
@ -138,7 +138,7 @@ export default class Modals {
if (err.error) {
error.find("a").on("click", (e) => {
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 ContentList from "./settings/contentlist";
import AddonList from "./settings/addonlist";
import SettingsGroup from "./settings/group";
import SettingsTitle from "./settings/title";
import Attribution from "./settings/attribution";
@ -34,11 +34,11 @@ export default new class SettingsRenderer {
})];
}
getContentPanel(title, contentList, contentState, options = {}) {
return React.createElement(ContentList, Object.assign({}, {
getAddonPanel(title, addonList, addonState, options = {}) {
return React.createElement(AddonList, Object.assign({}, {
title: title,
contentList: contentList,
contentState: contentState
addonList: addonList,
addonState: addonState
}, 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 ReloadIcon from "../icons/reload";
export default class ContentList extends React.Component {
export default class AddonList extends React.Component {
reload() {
if (this.props.refreshList) this.props.refreshList();
@ -13,15 +13,15 @@ export default class ContentList extends React.Component {
}
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 button = folder ? {title: Strings.Addons.openFolder.format({type: title}), onClick: () => {require("electron").shell.openItem(folder);}} : null;
return [
<SettingsTitle key="title" text={title} button={button} otherChildren={showReloadIcon && <ReloadIcon className="bd-reload" onClick={this.reload.bind(this)} />} />,
<ul key="ContentList" className={"bda-slist"}>
{contentList.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())).map(content => {
const CardType = content.type ? PluginCard : ThemeCard;
return <CardType showReloadIcon={showReloadIcon} key={content.id} enabled={contentState[content.id]} content={content} onChange={onChange} reload={reload} />;
<ul key="addonList" className={"bd-slist"}>
{addonList.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())).map(addon => {
const CardType = addon.type ? PluginCard : ThemeCard;
return <CardType showReloadIcon={showReloadIcon} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} />;
})}
</ul>
];

View File

@ -15,7 +15,7 @@ export default class BBDAttribution extends React.Component {
}
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>)}
</div>;
}

View File

@ -12,7 +12,7 @@ export default class PluginCard extends React.Component {
checked: this.props.enabled,
settingsOpen: false
};
this.hasSettings = typeof this.props.content.plugin.getSettingsPanel === "function";
this.hasSettings = typeof this.props.addon.plugin.getSettingsPanel === "function";
this.settingsPanel = "";
this.panelRef = React.createRef();
@ -23,7 +23,7 @@ export default class PluginCard extends React.Component {
reload() {
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();
}
@ -58,41 +58,41 @@ export default class PluginCard extends React.Component {
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: "bda-name"}, 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: "bda-version"}, 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: "bda-author"}, author);
if (nameIndex) title[authorIndex] = React.createElement("span", {className: "bd-author"}, author);
return title.flat();
}
get settingsComponent() {
const content = this.props.content;
const name = this.getString(content.name);
try { this.settingsPanel = content.plugin.getSettingsPanel(); }
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 ui-switch-item">
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.content[which];
const url = this.props.addon[which];
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() {
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);
return <div className="bda-footer">
<span className="bda-links">{linkComponents.map((comp, i) => i < linkComponents.length - 1 ? [comp, " | "] : [comp]).flat()}</span>
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>;
}
@ -100,31 +100,31 @@ export default class PluginCard extends React.Component {
render() {
if (this.state.settingsOpen) return this.settingsComponent;
const {content} = this.props;
const name = this.getString(content.name);
const author = this.getString(content.author);
const description = this.getString(content.description);
const version = this.getString(content.version);
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 ui-switch-item">
<div className="bda-header">
<span className="bda-header-title">{this.buildTitle(name, version, author)}</span>
<div className="bda-controls">
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="ui-switch-wrapper ui-flex-child">
<input className="ui-switch-checkbox" checked={this.state.checked} onChange={this.onChange} type="checkbox" />
<div className={this.state.checked ? "ui-switch checked" : "ui-switch"} />
<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="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}
</li>;
}
onChange() {
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() {

View File

@ -7,7 +7,7 @@ export default class ThemeCard extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: this.props.enabled, //ThemeManager.isEnabled(this.props.content.id),
checked: this.props.enabled,
reloads: 0
};
this.onChange = this.onChange.bind(this);
@ -16,51 +16,51 @@ export default class ThemeCard extends React.Component {
reload() {
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();
}
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: "bda-name"}, 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: "bda-version"}, 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: "bda-author"}, author);
if (nameIndex) title[authorIndex] = React.createElement("span", {className: "bd-author"}, author);
return title.flat();
}
render() {
const {content} = this.props;
const name = content.name;
const description = content.description;
const version = content.version;
const author = content.author;
const website = content.website;
const source = content.source;
const {addon} = this.props;
const name = addon.name;
const description = addon.description;
const version = addon.version;
const author = addon.author;
const website = addon.website;
const source = addon.source;
return React.createElement("li", {"data-name": name, "data-version": version, "className": "settings-closed ui-switch-item"},
React.createElement("div", {className: "bda-header"},
React.createElement("span", {className: "bda-header-title"},
return React.createElement("li", {"data-name": name, "data-version": version, "className": "settings-closed bd-switch-item"},
React.createElement("div", {className: "bd-header"},
React.createElement("span", {className: "bd-header-title"},
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}),
React.createElement("label", {className: "ui-switch-wrapper ui-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("div", {className: this.state.checked ? "ui-switch checked" : "ui-switch"})
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: "bd-switch-checkbox", type: "checkbox"}),
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: "bda-description scroller"}, description)
React.createElement("div", {className: "bd-description-wrap scroller-wrap fade"},
React.createElement("div", {className: "bd-description scroller"}, description)
),
(website || source) && React.createElement("div", {className: "bda-footer"},
React.createElement("span", {className: "bda-links"},
website && React.createElement("a", {className: "bda-link", href: website, target: "_blank"}, "Website"),
(website || source) && React.createElement("div", {className: "bd-footer"},
React.createElement("span", {className: "bd-links"},
website && React.createElement("a", {className: "bd-link", href: website, target: "_blank"}, "Website"),
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() {
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);
}
}