From 5b8f2f2de3e318d8961012ea11fb673c792a2e28 Mon Sep 17 00:00:00 2001 From: Zack Rauen Date: Mon, 2 Nov 2020 20:47:08 -0500 Subject: [PATCH] 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 --- src/data/settings/config.js | 1 - src/data/strings.js | 6 +- src/modules/addonmanager.js | 10 +- src/modules/datastore.js | 2 +- src/styles/builtins/publicservers.css | 4 + src/styles/buttons.css | 21 ++++ src/styles/index.css | 1 + src/styles/search.css | 27 +++++ src/styles/ui/addonlist.css | 99 +++++++++++-------- src/ui/errorboundary.jsx | 1 + src/ui/floating/container.jsx | 19 ++-- src/ui/icons/dollarsign.jsx | 11 +++ src/ui/icons/github.jsx | 10 ++ src/ui/icons/globe.jsx | 11 +++ src/ui/icons/patreon.jsx | 11 +++ src/ui/icons/support.jsx | 11 +++ src/ui/modals.js | 59 ++++++++++- src/ui/publicservers/menu.js | 6 +- src/ui/settings/addoncard.jsx | 137 ++++++++++---------------- 19 files changed, 300 insertions(+), 147 deletions(-) create mode 100644 src/styles/search.css create mode 100644 src/ui/icons/dollarsign.jsx create mode 100644 src/ui/icons/github.jsx create mode 100644 src/ui/icons/globe.jsx create mode 100644 src/ui/icons/patreon.jsx create mode 100644 src/ui/icons/support.jsx diff --git a/src/data/settings/config.js b/src/data/settings/config.js index baa5e4b0..ccd72c32 100644 --- a/src/data/settings/config.js +++ b/src/data/settings/config.js @@ -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"}]} ] diff --git a/src/data/strings.js b/src/data/strings.js index a2658907..44b8af51 100644 --- a/src/data/strings.js +++ b/src/data/strings.js @@ -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", diff --git a/src/modules/addonmanager.js b/src/modules/addonmanager.js index 57f8e567..e4887c8b 100644 --- a/src/modules/addonmanager.js +++ b/src/modules/addonmanager.js @@ -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, diff --git a/src/modules/datastore.js b/src/modules/datastore.js index 6a266d05..cf9bd7b7 100644 --- a/src/modules/datastore.js +++ b/src/modules/datastore.js @@ -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} }; diff --git a/src/styles/builtins/publicservers.css b/src/styles/builtins/publicservers.css index aa67887b..34575588 100644 --- a/src/styles/builtins/publicservers.css +++ b/src/styles/builtins/publicservers.css @@ -241,4 +241,8 @@ .bd-pagination button[disabled] { opacity: 0.2; cursor: not-allowed; +} + +.bd-pagination + .bd-settings-title { + margin-top: 20px; } \ No newline at end of file diff --git a/src/styles/buttons.css b/src/styles/buttons.css index a1f3c7fa..c878aa80 100644 --- a/src/styles/buttons.css +++ b/src/styles/buttons.css @@ -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; } \ No newline at end of file diff --git a/src/styles/index.css b/src/styles/index.css index 7e1a3483..ab429b84 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -4,6 +4,7 @@ @import "./ui/*"; @import "./buttons.css"; @import "./spinner.css"; +@import "./search.css"; .bd-chat-badge { vertical-align: bottom; diff --git a/src/styles/search.css b/src/styles/search.css new file mode 100644 index 00000000..3354f08d --- /dev/null +++ b/src/styles/search.css @@ -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); +} \ No newline at end of file diff --git a/src/styles/ui/addonlist.css b/src/styles/ui/addonlist.css index 5b7e7016..7f0e9bc8 100644 --- a/src/styles/ui/addonlist.css +++ b/src/styles/ui/addonlist.css @@ -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); } \ No newline at end of file diff --git a/src/ui/errorboundary.jsx b/src/ui/errorboundary.jsx index fee9e3fa..e88afb9a 100644 --- a/src/ui/errorboundary.jsx +++ b/src/ui/errorboundary.jsx @@ -1,5 +1,6 @@ import {React, Logger} from "modules"; import {remote} from "electron"; + export default class ErrorBoundary extends React.Component { constructor(props) { super(props); diff --git a/src/ui/floating/container.jsx b/src/ui/floating/container.jsx index 17a4eea4..be6069f1 100644 --- a/src/ui/floating/container.jsx +++ b/src/ui/floating/container.jsx @@ -17,24 +17,27 @@ class FloatingWindowContainer extends React.Component { render() { return this.state.windows.map(window => - + {window.children} ); } 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; + }) + }; }); } diff --git a/src/ui/icons/dollarsign.jsx b/src/ui/icons/dollarsign.jsx new file mode 100644 index 00000000..3352af7a --- /dev/null +++ b/src/ui/icons/dollarsign.jsx @@ -0,0 +1,11 @@ +import {React} from "modules"; + +export default class DollarSign extends React.Component { + render() { + const size = this.props.size || "18px"; + return + + + ; + } +} \ No newline at end of file diff --git a/src/ui/icons/github.jsx b/src/ui/icons/github.jsx new file mode 100644 index 00000000..180ff1ff --- /dev/null +++ b/src/ui/icons/github.jsx @@ -0,0 +1,10 @@ +import {React} from "modules"; + +export default class GitHub extends React.Component { + render() { + const size = this.props.size || "18px"; + return + + ; + } +} \ No newline at end of file diff --git a/src/ui/icons/globe.jsx b/src/ui/icons/globe.jsx new file mode 100644 index 00000000..4ad7dd13 --- /dev/null +++ b/src/ui/icons/globe.jsx @@ -0,0 +1,11 @@ +import {React} from "modules"; + +export default class Globe extends React.Component { + render() { + const size = this.props.size || "18px"; + return + + + ; + } +} \ No newline at end of file diff --git a/src/ui/icons/patreon.jsx b/src/ui/icons/patreon.jsx new file mode 100644 index 00000000..f408a7e3 --- /dev/null +++ b/src/ui/icons/patreon.jsx @@ -0,0 +1,11 @@ +import {React} from "modules"; + +export default class Patreon extends React.Component { + render() { + const size = this.props.size || "18px"; + return + + + ; + } +} \ No newline at end of file diff --git a/src/ui/icons/support.jsx b/src/ui/icons/support.jsx new file mode 100644 index 00000000..fbe7413b --- /dev/null +++ b/src/ui/icons/support.jsx @@ -0,0 +1,11 @@ +import {React} from "modules"; + +export default class Support extends React.Component { + render() { + const size = this.props.size || "18px"; + return + + + ; + } +} \ No newline at end of file diff --git a/src/ui/modals.js b/src/ui/modals.js index 4f73afdc..3ec915a3 100644 --- a/src/ui/modals.js +++ b/src/ui/modals.js @@ -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(`
@@ -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); + }); + } } \ No newline at end of file diff --git a/src/ui/publicservers/menu.js b/src/ui/publicservers/menu.js index 242cab02..777240b2 100644 --- a/src/ui/publicservers/menu.js +++ b/src/ui/publicservers/menu.js @@ -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}) ]; } diff --git a/src/ui/settings/addoncard.jsx b/src/ui/settings/addoncard.jsx index 1aad2688..b368b2f2 100644 --- a/src/ui/settings/addoncard.jsx +++ b/src/ui/settings/addoncard.jsx @@ -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 = ; - if (this.settingsPanel.$$typeof && this.settingsPanel.$$typeof === Symbol.for("react.element")) child = this.settingsPanel; - if (child) child = {child}; - - return
-
-
- {child} -
-
; - } - buildLink(which) { const url = this.props.addon[which]; if (!url) return null; - const link = {Strings.Addons[which]}; + const icon = React.createElement(LinkIcons[which]); + const link = {icon}; 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 && } + return
+ {this.props.hasSettings && this.makeControlButton(Strings.Addons.addonSettings, , this.showSettings, {disabled: !this.props.enabled})} + {this.props.showReloadIcon && this.makeControlButton(Strings.Addons.reload, , this.reload)} + {this.props.editAddon && this.makeControlButton(Strings.Addons.editAddon, , this.props.editAddon)} + {this.props.deleteAddon && this.makeControlButton(Strings.Addons.deleteAddon, , this.props.deleteAddon, {danger: true})} +
; } 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
- {linkComponents.map((comp, i) => i < linkComponents.length - 1 ? [comp, " | "] : comp).flat()} - {this.props.hasSettings && } + {linkComponents} + {this.controls}
; } @@ -151,9 +121,15 @@ export default class AddonCard extends React.Component { ; } - render() { - if (this.state.settingsOpen) return this.settingsComponent; + makeControlButton(title, children, action, {danger = false, disabled = false} = {}) { + return + {(props) => { + return ; + }} + ; + } + 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
{this.buildTitle(name, version, author)} -
- {this.props.editAddon && this.makeButton(Strings.Addons.editAddon, , this.props.editAddon)} - {this.props.deleteAddon && this.makeButton(Strings.Addons.deleteAddon, , this.props.deleteAddon)} - {this.props.showReloadIcon && this.makeButton(Strings.Addons.reload, , this.reload)} - -
+
{description}
{this.footer}