Adds facelist for addon content

- Redesign of addon cards
- Addon settings moved to modals
- Fixes error with extra title in PS
- Fixes error with pagination in PS
- Fixes several issues with detached windows
This commit is contained in:
Zack Rauen 2020-11-02 20:47:08 -05:00
parent 67c29bbce5
commit 5b8f2f2de3
19 changed files with 300 additions and 147 deletions

View File

@ -33,7 +33,6 @@ export default [
shown: false,
settings: [
{type: "switch", id: "addonErrors", value: true},
{type: "switch", id: "autoScroll", value: true},
{type: "switch", id: "autoReload", value: true},
{type: "dropdown", id: "editAction", value: "detached", options: [{value: "detached"}, {value: "system"}]}
]

View File

@ -71,10 +71,6 @@ export default {
name: "Show Addon Errors",
note: "Shows a modal with plugin/theme errors"
},
autoScroll: {
name: "Scroll To Settings",
note: "Auto-scrolls to a plugin's settings when the button is clicked (only if out of view)"
},
autoReload: {
name: "Automatic Loading",
note: "Automatically loads, reloads, and unloads plugins and themes"
@ -223,6 +219,7 @@ export default {
couldNotDisable: "{{name}} could not be disabled.",
couldNotStart: "{{name}} could not be started.",
couldNotStop: "{{name}} could not be stopped.",
settingsError: "Could not open settings for {{name}}",
methodError: "{{method}} could not be fired.",
unknownAuthor: "Unknown Author",
noDescription: "Description not provided.",
@ -276,6 +273,7 @@ export default {
Modals: {
confirmAction: "Are You Sure?",
okay: "Okay",
done: "Done",
cancel: "Cancel",
nevermind: "Nevermind",
close: "Close",

View File

@ -46,6 +46,7 @@ export default class AddonManager {
this.timeCache = {};
this.addonList = [];
this.state = {};
this.windows = new Set();
}
initialize() {
@ -343,9 +344,12 @@ export default class AddonManager {
const fullPath = path.resolve(this.addonFolder, addon.filename);
const content = fs.readFileSync(fullPath).toString();
if (this.windows.has(fullPath)) return;
this.windows.add(fullPath);
const editorRef = React.createRef();
const editor = React.createElement(AddonEditor, {
id: "bd-floating-editor-" + addon.name,
id: "bd-floating-editor-" + addon.id,
ref: editorRef,
content: content,
save: this.saveAddon.bind(this, addon),
@ -355,14 +359,14 @@ export default class AddonManager {
FloatingWindows.open({
onClose: () => {
this.isDetached = false;
this.windows.delete(fullPath);
},
onResize: () => {
if (!editorRef || !editorRef.current || !editorRef.current.resize) return;
editorRef.current.resize();
},
title: addon.name,
id: content.id,
id: "bd-floating-window-" + addon.id,
className: "floating-addon-window",
height: 470,
width: 410,

View File

@ -60,7 +60,7 @@ export default new class DataStore {
const newSettings = {
general: {publicServers: oldSettings["bda-gs-1"], voiceDisconnect: oldSettings["bda-dc-0"], classNormalizer: oldSettings["fork-ps-4"], showToasts: oldSettings["fork-ps-2"]},
appearance: {twentyFourHour: oldSettings["bda-gs-6"], voiceMode: oldSettings["bda-gs-4"], minimalMode: oldSettings["bda-gs-2"], hideChannels: oldSettings["bda-gs-3"], darkMode: oldSettings["bda-gs-5"], coloredText: oldSettings["bda-gs-7"]},
addons: {addonErrors: oldSettings["fork-ps-1"], autoScroll: oldSettings["fork-ps-3"], autoReload: oldSettings["fork-ps-5"]},
addons: {addonErrors: oldSettings["fork-ps-1"], autoReload: oldSettings["fork-ps-5"]},
developer: {debuggerHotkey: oldSettings["bda-gs-8"], copySelector: oldSettings["fork-dm-1"], reactDevTools: oldSettings.reactDevTools}
};

View File

@ -241,4 +241,8 @@
.bd-pagination button[disabled] {
opacity: 0.2;
cursor: not-allowed;
}
.bd-pagination + .bd-settings-title {
margin-top: 20px;
}

View File

@ -3,6 +3,7 @@
color: #fff;
border-radius: 3px;
padding: 3px 6px;
transition: opacity 250ms ease;
}
.bd-button:hover {
@ -23,4 +24,24 @@
.bd-button.bd-button-success:active {
background-color: rgb(46, 154, 74);
}
.bd-button.bd-button-danger {
background-color: #f04747;
}
.bd-button.bd-button-danger:hover {
background-color: rgb(237, 42, 42);
}
.bd-button.bd-button-danger:active {
background-color: rgb(230, 18, 18);
}
.bd-button-disabled {
opacity: 0.4;
}
.bd-button-disabled:hover {
cursor: not-allowed;
}

View File

@ -4,6 +4,7 @@
@import "./ui/*";
@import "./buttons.css";
@import "./spinner.css";
@import "./search.css";
.bd-chat-badge {
vertical-align: bottom;

27
src/styles/search.css Normal file
View File

@ -0,0 +1,27 @@
.bd-search-wrapper {
padding: 3px;
border-radius: 3px;
outline: none;
border: 0;
background-color: var(--background-tertiary);
color: var(--text-muted);
display: flex;
align-items: center;
}
.bd-search {
padding: 2px 3px;
background: none;
border: 0;
color: var(--text-normal);
flex: 1;
}
.bd-search::-webkit-input-placeholder {
color: var(--text-muted);
}
.bd-search-wrapper > svg {
margin-right: 2px;
fill: var(--interactive-normal);
}

View File

@ -27,8 +27,7 @@
.bd-addon-list .bd-addon-card {
max-height: 175px;
margin-bottom: 20px;
padding: 5px 8px;
border: 1px solid transparent;
padding: 12px;
border-radius: 5px;
overflow: hidden;
}
@ -36,13 +35,11 @@
.theme-dark .bd-addon-list .bd-addon-card {
background-color: rgba(32, 34, 37, 0.6);
color: #f6f6f7;
border-color: #202225;
}
.theme-light .bd-addon-list .bd-addon-card {
background-color: #f8f9f9;
color: #4f545c;
border-color: #dcddde;
}
.bd-addon-list .bd-addon-card.settings-open {
@ -51,24 +48,20 @@
}
.bd-addon-list .bd-addon-header {
font-size: 12px;
font-size: 14px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 5px;
border-bottom: 1px solid transparent;
overflow: hidden;
}
.theme-dark .bd-addon-list .bd-addon-header {
color: #f6f6f7;
border-bottom-color: rgba(114, 118, 125, 0.3);
}
.theme-light .bd-addon-list .bd-addon-header {
color: #4f545c;
border-bottom-color: rgba(185, 187, 190, 0.3);
}
.bd-addon-list .bd-description {
@ -77,6 +70,7 @@
margin: 5px 0;
padding: 5px 0;
overflow-y: auto;
line-height: 1.125em;
}
.theme-dark .bd-addon-list .bd-description {
@ -99,7 +93,7 @@
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 5px;
padding-top: 8px;
border-top: 1px solid transparent;
overflow: hidden;
}
@ -112,15 +106,6 @@
border-top-color: rgba(185, 187, 190, 0.3);
}
.bd-addon-list .bd-footer button {
padding: 3px 16px;
transition: opacity 250ms ease;
}
.bd-addon-list .bd-footer button:disabled {
opacity: 0.4;
}
.bd-addon-list .bd-footer a {
color: #3e82e5;
}
@ -129,44 +114,48 @@
text-decoration: underline;
}
.bd-controls + .bd-addon-list {
margin-top: 10px;
.bd-controls > .bd-addon-button {
border-radius: 0;
}
.bd-addon-button {
cursor: pointer;
.bd-links .bd-addon-button + .bd-addon-button {
margin-left: 10px;
}
.bd-addon-button + .bd-addon-button {
margin-left: 5px;
.bd-controls > .bd-addon-button svg {
fill: #fff;
}
.bd-search-wrapper {
padding: 3px;
border-radius: 3px;
outline: none;
border: 0;
background-color: var(--background-tertiary);
color: var(--text-muted);
.bd-controls > .bd-addon-button:first-of-type {
border-radius: 5px 0 0 5px;
}
.bd-controls > .bd-addon-button:last-of-type {
border-radius: 0 5px 5px 0;
}
.bd-addon-list .bd-footer .bd-links,
.bd-addon-list .bd-footer .bd-links a,
.bd-addon-list .bd-footer .bd-addon-button {
display: flex;
align-items: center;
}
.bd-search {
padding: 2px 3px;
background: none;
border: 0;
color: var(--text-normal);
flex: 1;
.bd-addon-list .bd-footer .bd-links .bd-addon-button {
height: 24px;
}
.bd-search::-webkit-input-placeholder {
color: var(--text-muted);
.bd-controls + .bd-addon-list {
margin-top: 10px;
}
.bd-search-wrapper > svg {
margin-right: 2px;
fill: var(--interactive-normal);
.bd-links .bd-addon-button svg {
opacity: 0.7;
}
.bd-links .bd-addon-button:active svg,
.bd-links .bd-addon-button:hover svg {
opacity: 1;
}
.bd-addon-controls {
@ -196,4 +185,28 @@
.settings-open .bd-close {
cursor: pointer;
float: right;
}
.bd-addon-modal {
min-height: unset;
}
.bd-addon-modal-settings {
/* padding: 16px; */
}
.bd-addon-modal-footer .bd-button {
background-color: #3e82e5;
color: #fff;
border-radius: 3px;
padding: 3px 6px;
transition: opacity 250ms ease;
}
.bd-addon-modal-footer .bd-button:hover {
background-color: rgb(56, 117, 206);
}
.bd-addon-modal-footer .bd-button:active {
background-color: rgb(50, 104, 183);
}

View File

@ -1,5 +1,6 @@
import {React, Logger} from "modules";
import {remote} from "electron";
export default class ErrorBoundary extends React.Component {
constructor(props) {
super(props);

View File

@ -17,24 +17,27 @@ class FloatingWindowContainer extends React.Component {
render() {
return this.state.windows.map(window =>
<FloatingWindow {...window} close={this.close.bind(this, window.id)} minY={this.minY}>
<FloatingWindow {...window} close={this.close.bind(this, window.id)} minY={this.minY} key={window.id}>
{window.children}
</FloatingWindow>
);
}
open(window) {
this.setState({
windows: [...this.state.windows, window]
this.setState(state => {
state.windows.push(window);
return {windows: state.windows};
});
}
close(id) {
this.setState({
windows: this.state.windows.filter(w => {
if (w.id == id && w.onClose) w.onClose();
return w.id != id;
})
this.setState(state => {
return {
windows: state.windows.filter(w => {
if (w.id == id && w.onClose) w.onClose();
return w.id != id;
})
};
});
}

View File

@ -0,0 +1,11 @@
import {React} from "modules";
export default class DollarSign extends React.Component {
render() {
const size = this.props.size || "18px";
return <svg viewBox="2 2 20 20" fill="#FFFFFF" style={{width: size, height: size}} onClick={this.props.onClick}>
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.41 16.09V20h-2.67v-1.93c-1.71-.36-3.16-1.46-3.27-3.4h1.96c.1 1.05.82 1.87 2.65 1.87 1.96 0 2.4-.98 2.4-1.59 0-.83-.44-1.61-2.67-2.14-2.48-.6-4.18-1.62-4.18-3.67 0-1.72 1.39-2.84 3.11-3.21V4h2.67v1.95c1.86.45 2.79 1.86 2.85 3.39H14.3c-.05-1.11-.64-1.87-2.22-1.87-1.5 0-2.4.68-2.4 1.64 0 .84.65 1.39 2.67 1.91s4.18 1.39 4.18 3.91c-.01 1.83-1.38 2.83-3.12 3.16z"/>
</svg>;
}
}

10
src/ui/icons/github.jsx Normal file
View File

@ -0,0 +1,10 @@
import {React} from "modules";
export default class GitHub extends React.Component {
render() {
const size = this.props.size || "18px";
return <svg viewBox="0 0 24 24" fill="#FFFFFF" style={{width: size, height: size}} onClick={this.props.onClick}>
<path d="m12 .5c-6.63 0-12 5.28-12 11.792 0 5.211 3.438 9.63 8.205 11.188.6.111.82-.254.82-.567 0-.28-.01-1.022-.015-2.005-3.338.711-4.042-1.582-4.042-1.582-.546-1.361-1.335-1.725-1.335-1.725-1.087-.731.084-.716.084-.716 1.205.082 1.838 1.215 1.838 1.215 1.07 1.803 2.809 1.282 3.495.981.108-.763.417-1.282.76-1.577-2.665-.295-5.466-1.309-5.466-5.827 0-1.287.465-2.339 1.235-3.164-.135-.298-.54-1.497.105-3.121 0 0 1.005-.316 3.3 1.209.96-.262 1.98-.392 3-.398 1.02.006 2.04.136 3 .398 2.28-1.525 3.285-1.209 3.285-1.209.645 1.624.24 2.823.12 3.121.765.825 1.23 1.877 1.23 3.164 0 4.53-2.805 5.527-5.475 5.817.42.354.81 1.077.81 2.182 0 1.578-.015 2.846-.015 3.229 0 .309.21.678.825.56 4.801-1.548 8.236-5.97 8.236-11.173 0-6.512-5.373-11.792-12-11.792z" />
</svg>;
}
}

11
src/ui/icons/globe.jsx Normal file
View File

@ -0,0 +1,11 @@
import {React} from "modules";
export default class Globe extends React.Component {
render() {
const size = this.props.size || "18px";
return <svg viewBox="2 2 20 20" fill="#FFFFFF" style={{width: size, height: size}} onClick={this.props.onClick}>
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"/>
</svg>;
}
}

11
src/ui/icons/patreon.jsx Normal file
View File

@ -0,0 +1,11 @@
import {React} from "modules";
export default class Patreon extends React.Component {
render() {
const size = this.props.size || "18px";
return <svg viewBox="0 0 24 24" fill="#FFFFFF" style={{width: size, height: size}} onClick={this.props.onClick}>
<path d="m0 .5h4.219v23h-4.219z"/>
<path d="m15.384.5c-4.767 0-8.644 3.873-8.644 8.633 0 4.75 3.877 8.61 8.644 8.61 4.754 0 8.616-3.865 8.616-8.61 0-4.759-3.863-8.633-8.616-8.633z"/>
</svg>;
}
}

11
src/ui/icons/support.jsx Normal file
View File

@ -0,0 +1,11 @@
import {React} from "modules";
export default class Support extends React.Component {
render() {
const size = this.props.size || "18px";
return <svg viewBox="2 2 20 20" fill="#FFFFFF" style={{width: size, height: size}} onClick={this.props.onClick}>
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"/>
</svg>;
}
}

View File

@ -1,6 +1,7 @@
import {Config} from "data";
import {Logger, WebpackModules, React, Settings, Strings, DOM, DiscordModules} from "modules";
import FormattableString from "../structs/string";
import ErrorBoundary from "./errorboundary";
export default class Modals {
@ -8,10 +9,15 @@ export default class Modals {
static get ModalActions() {return WebpackModules.getByProps("openModal", "updateModal");}
static get ModalStack() {return WebpackModules.getByProps("push", "update", "pop", "popWithKey");}
static get ModalComponents() {return WebpackModules.getByProps("ModalRoot");}
static get ModalClasses() {return WebpackModules.getByProps("modal", "content");}
static get AlertModal() {return WebpackModules.getByPrototypes("handleCancel", "handleSubmit", "handleMinorConfirm");}
static get FlexElements() {return WebpackModules.getByProps("Child", "Align");}
static get FormTitle() {return WebpackModules.findByDisplayName("FormTitle");}
static get TextElement() {return WebpackModules.getByProps("Sizes", "Weights");}
static get ConfirmationModal() {return WebpackModules.findByDisplayName("ConfirmModal");}
static get Markdown() {return WebpackModules.findByDisplayName("Markdown");}
static get Buttons() {return WebpackModules.getByProps("ButtonColors");}
static default(title, content) {
const modal = DOM.createElement(`<div class="bd-modal-wrapper theme-dark">
@ -78,7 +84,7 @@ export default class Modals {
return ModalActions.openModal(props => {
return React.createElement(ConfirmationModal, Object.assign({
header: title,
red: danger,
confirmButtonColor: danger ? this.Buttons.ButtonColors.RED : this.Buttons.ButtonColors.PRIMARY,
confirmText: confirmText,
cancelText: cancelText,
onConfirm: onConfirm,
@ -230,4 +236,55 @@ export default class Modals {
};
return key;
}
static showAddonSettingsModal(name, panel) {
let child = panel;
if (panel instanceof Node || typeof(panel) === "string") {
child = class ReactWrapper extends React.Component {
constructor(props) {
super(props);
this.elementRef = React.createRef();
this.element = panel;
}
componentDidMount() {
if (this.element instanceof Node) this.elementRef.current.appendChild(this.element);
// if (typeof(this.element) === "string") this.elementRef.current.appendChild(this.element);
}
render() {
const props = {
className: "bd-addon-settings-wrap",
ref: this.elementRef
};
if (typeof(this.element) === "string") props.dangerouslySetInnerHTML = {__html: this.element};
return React.createElement("div", props);
}
};
}
if (typeof(child) === "function") child = React.createElement(child);
const mc = this.ModalComponents;
const modal = props => {
return React.createElement(mc.ModalRoot, Object.assign({size: mc.ModalSize.MEDIUM, className: "bd-addon-modal"}, props),
React.createElement(mc.ModalHeader, {separator: false, className: "bd-addon-modal-header"},
React.createElement(this.FormTitle, {tag: "h4"}, `${name} Settings`),
React.createElement(this.FlexElements.Child, {grow: 0},
React.createElement(mc.ModalCloseButton, {onClick: props.onClose})
)
),
React.createElement(mc.ModalContent, {className: "bd-addon-modal-settings"},
React.createElement(ErrorBoundary, {}, child)
),
React.createElement(mc.ModalFooter, {className: "bd-addon-modal-footer"},
React.createElement(this.Buttons.default, {onClick: props.onClose, className: "bd-button"}, Strings.Modals.done)
)
);
};
return this.ModalActions.openModal(props => {
return React.createElement(modal, props);
});
}
}

View File

@ -164,10 +164,10 @@ export default class PublicServers extends React.Component {
else if (this.state.results.total) content = React.createElement("div", {className: "bd-card-list"}, servers);
return [React.createElement(SettingsTitle, {text: this.title, button: connectButton}),
(this.state.tab !== "Featured" && this.state.tab !== "Popular") && this.pagination,
this.state.results.numPages > 1 && this.pagination,
content,
(this.state.tab !== "Featured" && this.state.tab !== "Popular") && this.pagination,
this.state.results.servers.length > 0 && React.createElement(SettingsTitle, {text: this.title})
this.state.results.numPages > 1 && this.pagination,
this.state.results.numPages > 1 && this.state.query && React.createElement(SettingsTitle, {text: this.title})
];
}

View File

@ -1,10 +1,25 @@
import {React, Logger, Strings, WebpackModules, DOM, DiscordModules} from "modules";
import CloseButton from "../icons/close";
import {React, Logger, Strings, WebpackModules, DiscordModules} from "modules";
import ReloadIcon from "../icons/reload";
import EditIcon from "../icons/edit";
import DeleteIcon from "../icons/delete";
import CogIcon from "../icons/cog";
import Switch from "./components/switch";
import ErrorBoundary from "../errorboundary";
import GitHubIcon from "../icons/github";
import MoneyIcon from "../icons/dollarsign";
import WebIcon from "../icons/globe";
import PatreonIcon from "../icons/patreon";
import SupportIcon from "../icons/support";
import Modals from "../modals";
import Toasts from "../toasts";
const LinkIcons = {
website: WebIcon,
source: GitHubIcon,
invite: SupportIcon,
donate: MoneyIcon,
patreon: PatreonIcon
};
const Tooltip = WebpackModules.getByDisplayName("Tooltip");
@ -22,7 +37,18 @@ export default class AddonCard extends React.Component {
this.onChange = this.onChange.bind(this);
this.reload = this.reload.bind(this);
this.showSettings = this.showSettings.bind(this);
this.closeSettings = this.closeSettings.bind(this);
}
showSettings() {
if (!this.props.hasSettings || !this.props.enabled) return;
const name = this.getString(this.props.addon.name);
try {
Modals.showAddonSettingsModal(name, this.props.getSettingsPanel());
}
catch (err) {
Toasts.show(Strings.Addons.settingsError.format({name}), {type: "error"});
Logger.stacktrace("Addon Settings", "Unable to get settings panel for " + name + ".", err);
}
}
reload() {
@ -31,36 +57,6 @@ export default class AddonCard extends React.Component {
this.forceUpdate();
}
componentDidUpdate() {
if (!this.state.settingsOpen) return;
if (this.settingsPanel instanceof Node) this.panelRef.current.appendChild(this.settingsPanel);
setImmediate(() => {
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 thisNode = this.panelRef.current;
const container = thisNode.closest(".scrollerBase-289Jih");
if (!container || !isHidden(container, thisNode)) return;
const thisNodeOffset = DOM.offset(thisNode);
const containerOffset = DOM.offset(container);
const original = container.scrollTop;
const endPoint = thisNodeOffset.top - containerOffset.top + container.scrollTop - 30;
DOM.animate({
duration: 300,
update: function(progress) {
if (endPoint > original) container.scrollTop = original + (progress * (endPoint - original));
else container.scrollTop = original - (progress * (original - endPoint));
}
});
});
}
getString(value) {return typeof value == "string" ? value : value.toString();}
onChange() {
@ -69,16 +65,6 @@ export default class AddonCard extends React.Component {
this.forceUpdate();
}
showSettings() {
if (!this.props.hasSettings) return;
this.setState({settingsOpen: true});
}
closeSettings() {
if (this.settingsPanel instanceof Node) 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}}");
@ -90,35 +76,11 @@ export default class AddonCard extends React.Component {
return title.flat();
}
get settingsComponent() {
const addon = this.props.addon;
const name = this.getString(addon.name);
try {this.settingsPanel = this.props.getSettingsPanel();}
catch (err) {Logger.stacktrace("Addon Settings", "Unable to get settings panel for " + name + ".", err);}
const props = {id: `${name}-settings`, className: "addon-settings", ref: this.panelRef};
if (typeof(this.settingsPanel) == "string") {
Logger.warn("Addon Settings", "Using a DOMString is officially deprecated.");
props.dangerouslySetInnerHTML = {__html: this.settingsPanel};
}
let child;
if (typeof(this.settingsPanel) === "function") child = <this.settingsPanel />;
if (this.settingsPanel.$$typeof && this.settingsPanel.$$typeof === Symbol.for("react.element")) child = this.settingsPanel;
if (child) child = <ErrorBoundary>{child}</ErrorBoundary>;
return <div className="bd-addon-card settings-open bd-switch-item">
<div className="bd-close" onClick={this.closeSettings}><CloseButton /></div>
<div {...props}>
{child}
</div>
</div>;
}
buildLink(which) {
const url = this.props.addon[which];
if (!url) return null;
const link = <a className="bd-link bd-link-website" href={url} target="_blank" rel="noopener noreferrer">{Strings.Addons[which]}</a>;
const icon = React.createElement(LinkIcons[which]);
const link = <a className="bd-link bd-link-website" href={url} target="_blank" rel="noopener noreferrer">{icon}</a>;
if (which == "invite") {
link.props.onClick = function(event) {
event.preventDefault();
@ -130,16 +92,24 @@ export default class AddonCard extends React.Component {
DiscordModules.InviteActions.acceptInviteAndTransitionToInviteChannel(code);
};
}
return link;
return this.makeButton(Strings.Addons[which], link);
}
get controls() { // {this.props.hasSettings && <button onClick={this.showSettings} className="bd-button bd-button-addon-settings" disabled={!this.props.enabled}>{Strings.Addons.addonSettings}</button>}
return <div className="bd-controls">
{this.props.hasSettings && this.makeControlButton(Strings.Addons.addonSettings, <CogIcon size={"24px"} />, this.showSettings, {disabled: !this.props.enabled})}
{this.props.showReloadIcon && this.makeControlButton(Strings.Addons.reload, <ReloadIcon />, this.reload)}
{this.props.editAddon && this.makeControlButton(Strings.Addons.editAddon, <EditIcon />, this.props.editAddon)}
{this.props.deleteAddon && this.makeControlButton(Strings.Addons.deleteAddon, <DeleteIcon />, this.props.deleteAddon, {danger: true})}
</div>;
}
get footer() {
const links = ["website", "source", "invite", "donate", "patreon"];
if (!links.some(l => this.props.addon[l]) && !this.props.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);// linkComponents.map((comp, i) => i < linkComponents.length - 1 ? [comp, " | "] : comp).flat()
return <div className="bd-footer">
<span className="bd-links">{linkComponents.map((comp, i) => i < linkComponents.length - 1 ? [comp, " | "] : comp).flat()}</span>
{this.props.hasSettings && <button onClick={this.showSettings} className="bd-button bd-button-addon-settings" disabled={!this.props.enabled}>{Strings.Addons.addonSettings}</button>}
<span className="bd-links">{linkComponents}</span>
{this.controls}
</div>;
}
@ -151,9 +121,15 @@ export default class AddonCard extends React.Component {
</Tooltip>;
}
render() {
if (this.state.settingsOpen) return this.settingsComponent;
makeControlButton(title, children, action, {danger = false, disabled = false} = {}) {
return <Tooltip color="black" position="top" text={title}>
{(props) => {
return <button {...props} className={"bd-button bd-addon-button" + (danger ? " bd-button-danger" : "") + (disabled ? " bd-button-disabled" : "")} onClick={action}>{children}</button>;
}}
</Tooltip>;
}
render() {
const addon = this.props.addon;
const name = this.getString(addon.name);
const author = this.getString(addon.author);
@ -163,12 +139,7 @@ export default class AddonCard extends React.Component {
return <div id={`${addon.id}-card`} className="bd-addon-card settings-closed">
<div className="bd-addon-header">
<span className="bd-title">{this.buildTitle(name, version, author)}</span>
<div className="bd-controls">
{this.props.editAddon && this.makeButton(Strings.Addons.editAddon, <EditIcon />, this.props.editAddon)}
{this.props.deleteAddon && this.makeButton(Strings.Addons.deleteAddon, <DeleteIcon />, this.props.deleteAddon)}
{this.props.showReloadIcon && this.makeButton(Strings.Addons.reload, <ReloadIcon className="bd-reload bd-reload-card" />, this.reload)}
<Switch checked={this.props.enabled} onChange={this.onChange} />
</div>
<Switch checked={this.props.enabled} onChange={this.onChange} />
</div>
<div className="bd-description-wrap scroller-wrap fade"><div className="bd-description scroller">{description}</div></div>
{this.footer}