Replaces Ace with Monaco

- Replaces Ace editor with the Monaco editor (the one from VSCode)
- Adds the option for listview vs gridview in addon lists
- Addon descriptions can use markdown again
- Added blankslates for no addon results and no addons installed (Thanks Tropical)
- Replaces BBD logos (social links and loading icon) with BD logos
This commit is contained in:
Zack Rauen 2020-11-03 19:45:36 -05:00
parent 056845c621
commit b0c5f6ab20
22 changed files with 256 additions and 100 deletions

View File

@ -26,11 +26,37 @@ export default new class CustomCSS extends Builtin {
}
async enabled() {
if (!window.ace) {
DOMManager.injectScript("ace-script", "https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.9/ace.js").then(() => {
if (window.require.original) window.require = window.require.original;
});
}
// if (!window.ace) {
// DOMManager.injectScript("ace-script", "https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.9/ace.js").then(() => {
// if (window.require.original) window.require = window.require.original;
// });
// }
Object.defineProperty(window, "MonacoEnvironment", {
value: {
getWorkerUrl: function() {
return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
self.MonacoEnvironment = {
baseUrl: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.20.0/min'
};
importScripts('https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.20.0/min/vs/base/worker/workerMain.min.js');`
)}`;
}
}
});
const commonjsLoader = window.require;
delete window.module; // Make monaco think this isn't a local node script or else it freaks out
DOMManager.linkStyle("monaco-style", "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.20.0/min/vs/editor/editor.main.min.css", {documentHead: true});
DOMManager.injectScript("monaco-script", "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.20.0/min/vs/loader.min.js").then(() => {
const amdLoader = window.require; // Grab Monaco's amd loader
window.require = commonjsLoader; // Revert to commonjs
amdLoader.config({paths: {vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.20.0/min/vs"}});
amdLoader(["vs/editor/editor.main"], () => {}); // exposes the monaco global
});
Settings.registerPanel(this.id, Strings.Panels.customcss, {
order: 2,
element: () => [<SettingsTitle text={Strings.CustomCSS.editorTitle} />, React.createElement(CSSEditor, {

View File

@ -229,7 +229,9 @@ export default {
missingNameData: "META missing name data.",
metaNotFound: "META was not found.",
compileError: "Could not be compiled.",
wasUnloaded: "{{name}} was unloaded."
wasUnloaded: "{{name}} was unloaded.",
blankSlateHeader: "You don't have any {{type}}!",
blankSlateMessage: "Grab some from [this website]({{link}}) and add them to your {{type}} folder."
},
CustomCSS: {
confirmationText: "You have unsaved changes to your Custom CSS. Closing this window will lose all those changes.",

View File

@ -2,7 +2,7 @@ const css = `/* BEGIN V2 LOADER */
/* =============== */
#bd-loading-icon {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+IDwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyBoZWlnaHQ9IjEwMCUiIHN0eWxlPSJmaWxsLXJ1bGU6ZXZlbm9kZDtjbGlwLXJ1bGU6ZXZlbm9kZDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7IiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWw6c3BhY2U9InByZXNlcnZlIiB3aWR0aD0iMTAwJSIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIj4gICAgPG1ldGFkYXRhIC8+ICAgIDxkZWZzPiAgICAgICAgPGZpbHRlciBpZD0ic2hhZG93MSI+ICAgICAgICA8ZmVEcm9wU2hhZG93IGR4PSIyMCIgZHk9IjAiIHN0ZERldmlhdGlvbj0iMjAiIGZsb29kLWNvbG9yPSJyZ2JhKDAsMCwwLDAuMzUpIi8+ICAgICAgICA8L2ZpbHRlcj4gICAgICAgIDxmaWx0ZXIgaWQ9InNoYWRvdzIiPiAgICAgICAgPGZlRHJvcFNoYWRvdyBkeD0iMTUiIGR5PSIwIiBzdGREZXZpYXRpb249IjIwIiBmbG9vZC1jb2xvcj0icmdiYSgyNTUsMjU1LDI1NSwwLjE1KSIvPiAgICAgICAgPC9maWx0ZXI+ICAgICAgICA8ZmlsdGVyIGlkPSJzaGFkb3czIj4gICAgICAgIDxmZURyb3BTaGFkb3cgZHg9IjEwIiBkeT0iMCIgc3RkRGV2aWF0aW9uPSIyMCIgZmxvb2QtY29sb3I9InJnYmEoMCwwLDAsMC4zNSkiLz4gICAgICAgIDwvZmlsdGVyPiAgICA8L2RlZnM+ICAgIDxnPiAgICAgICAgPHBhdGggc3R5bGU9ImZpbHRlcjogdXJsKCNzaGFkb3czKSIgZD0iTTExOTUuNDQrMTM1LjQ0MkwxMTk1LjQ0KzEzNS40NDJMOTk3LjYrMTM2LjQ0MkMxMDI0LjIrMTQ5Ljc0MisxMTcwLjM0KzE2My41NDIrMTE5My42NCsxNzkuNzQyQzEyNjQuMzQrMjI4Ljg0MisxMzE5Ljc0KzI5MS4yNDIrMTM1OC4yNCszNjUuMDQyQzEzOTguMTQrNDQxLjY0MisxNDE5Ljc0KzUzMC42NDIrMTQyMi41NCs2MjkuNjQyTDE0MjIuNTQrNjMwLjg0MkwxNDIyLjU0KzYzMi4wNDJDMTQyMi41NCs3NzMuMTQyKzE0MjIuNTQrMTIyOC4xNCsxNDIyLjU0KzEzNjkuMTRMMTQyMi41NCsxMzcwLjM0TDE0MjIuNTQrMTM3MS41NEMxNDE5Ljg0KzE0NzAuNTQrMTM5OC4yNCsxNTU5LjU0KzEzNTguMjQrMTYzNi4xNEMxMzE5Ljc0KzE3MDkuOTQrMTI2NC40NCsxNzcyLjM0KzExOTMuNjQrMTgyMS40NEMxMTcxLjA0KzE4MzcuMTQrMTAyNS43KzE4NTAuNTQrMTAwMCsxODYzLjU0TDExOTMuNTQrMTg2NC41NEMxNTM5Ljc0KzE4NjYuNDQrMTg2NC41NCsxNjkzLjM0KzE4NjQuNTQrMTI5Ni42NEwxODY0LjU0KzcxNi45NDJDMTg2Ni40NCszMTIuNDQyKzE1NDEuNjQrMTM1LjQ0MisxMTk1LjQ0KzEzNS40NDJaIiBmaWxsPSIjMTcxNzE3IiBvcGFjaXR5PSIxIi8+ICAgICAgICA8cGF0aCBzdHlsZT0iZmlsdGVyOiB1cmwoI3NoYWRvdzIpIiBkPSJNMTY5NS41NCs2MzEuNDQyQzE2ODUuODQrMjc4LjA0MisxNDA5LjM0KzEzNS40NDIrMTA1Mi45NCsxMzUuNDQyTDM2MS43NCsxMzYuNDQyTDgwMy43NCs0OTAuNDQyTDEwNjAuNzQrNDkwLjQ0MkMxMzM1LjI0KzQ5MC40NDIrMTMzNS4yNCs4MzUuMzQyKzEwNjAuNzQrODM1LjM0MkwxMDYwLjc0KzExNjQuODRDMTE1MC4yMisxMTY0Ljg0KzEyMTAuNTMrMTIwMS40OCsxMjQxLjY4KzEyNTAuODdDMTMwNi4wNysxMzUzKzEyNDUuNzYrMTUwOS42NCsxMDYwLjc0KzE1MDkuNjRMMzYxLjc0KzE4NjMuNTRMMTA1Mi45NCsxODY0LjU0QzE0MDkuMjQrMTg2NC41NCsxNjg1Ljc0KzE3MjEuOTQrMTY5NS41NCsxMzY4LjU0QzE2OTUuNTQrMTIwNS45NCsxNjUxLjA0KzEwODQuNDQrMTU3Mi42NCs5OTkuOTQyQzE2NTEuMDQrOTE1LjU0MisxNjk1LjU0Kzc5NC4wNDIrMTY5NS41NCs2MzEuNDQyWiIgZmlsbD0iIzNFODJFNSIgb3BhY2l0eT0iMSIvPiAgICAgICAgPHBhdGggc3R5bGU9ImZpbHRlcjogdXJsKCNzaGFkb3cxKSIgZD0iTTE0NjkuMjUrNjMxLjQ0MkMxNDU5LjU1KzI3OC4wNDIrMTE4My4wNSsxMzUuNDQyKzgyNi42NSsxMzUuNDQyTDEzNS40NSsxMzUuNDQyTDEzNS40NSsxMDA0QzEzNS40NSsxMDA0KzEzNS40MjcrMTI1NS4yMSszNTUuNjI2KzEyNTUuMjFDNTc1LjgyNSsxMjU1LjIxKzU3NS44NDgrMTAwNCs1NzUuODQ4KzEwMDRMNTc3LjQ1KzQ5MC40NDJMODM0LjQ1KzQ5MC40NDJDMTEwOC45NSs0OTAuNDQyKzExMDguOTUrODM1LjM0Mis4MzQuNDUrODM1LjM0Mkw2NjQuNjUrODM1LjM0Mkw2NjQuNjUrMTE2NC44NEw4MzQuNDUrMTE2NC44NEM5MjMuOTMyKzExNjQuODQrOTg0LjI0NCsxMjAxLjQ4KzEwMTUuMzkrMTI1MC44N0MxMDc5Ljc4KzEzNTMrMTAxOS40NysxNTA5LjY0KzgzNC40NSsxNTA5LjY0TDEzNS40NSsxNTA5LjY0TDEzNS40NSsxODY0LjU0TDgyNi42NSsxODY0LjU0QzExODIuOTUrMTg2NC41NCsxNDU5LjQ1KzE3MjEuOTQrMTQ2OS4yNSsxMzY4LjU0QzE0NjkuMjUrMTIwNS45NCsxNDI0Ljc1KzEwODQuNDQrMTM0Ni4zNSs5OTkuOTQyQzE0MjQuNzUrOTE1LjU0MisxNDY5LjI1Kzc5NC4wNDIrMTQ2OS4yNSs2MzEuNDQyWiIgZmlsbD0iI0ZGRkZGRiIgb3BhY2l0eT0iMSIvPiAgICA8L2c+PC9zdmc+);
background-image: url(data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg==);
}
#bd-loading-icon {
position: fixed;

View File

@ -54,14 +54,15 @@ export default class DOMManager {
return this.removeStyle(id);
}
static linkStyle(id, url) {
static linkStyle(id, url, {documentHead = false} = {}) {
id = this.escapeID(id);
return new Promise(resolve => {
const link = this.getElement(`#${id}`, this.bdStyles) || this.createElement("link", {id});
link.rel = "stylesheet";
link.href = url;
link.onload = resolve;
this.bdStyles.append(link);
const target = documentHead ? document.head : this.bdStyles;
target.append(link);
});
}

View File

@ -0,0 +1,21 @@
.bd-empty-image-container {
background: transparent;
}
.bd-empty-image-header {
color: var(--header-primary);
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
}
.bd-empty-image-message {
color: var(--header-secondary);
margin-bottom: 8px;
}
.bd-empty-image-container .bd-button {
margin-top: 10px;
font-size: 16px;
padding: 10px 16px;
}

View File

@ -43,9 +43,8 @@
display: flex;
}
.editor,
.ace_editor {
line-height: normal;
font-family: Consolas, monospace;
box-sizing: border-box;
height: calc(100vh - 250px);
font-size: 14px;

View File

@ -1,4 +1,7 @@
.bd-button {
display: inline-flex;
justify-content: center;
align-items: center;
background-color: #3e82e5;
color: #fff;
border-radius: 3px;
@ -31,11 +34,11 @@
}
.bd-button.bd-button-danger:hover {
background-color: rgb(237, 42, 42);
background-color: #d84040;
}
.bd-button.bd-button-danger:active {
background-color: rgb(230, 18, 18);
background-color: #c03939;
}
.bd-button-disabled {

View File

@ -2,6 +2,7 @@
@import "./builtins/*";
@import "./ui/*";
@import "./blankslates/*";
@import "./buttons.css";
@import "./spinner.css";
@import "./search.css";

View File

@ -16,7 +16,8 @@
margin-right: 5px;
}
.bd-controls {
.bd-controls,
.bd-controls-advanced {
display: flex;
}
@ -24,18 +25,26 @@
user-select: text;
}
.bd-addon-list.bd-grid-view {
display: grid;
grid-template-columns: auto auto;
column-gap: 10px;
row-gap: 10px;
}
.bd-addon-list .bd-addon-card {
max-height: 175px;
display: flex;
flex-direction: column;
margin-bottom: 20px;
padding: 12px;
padding: 16px;
border-radius: 5px;
overflow: hidden;
background: var(--background-secondary-alt);
border: 1px solid var(--background-tertiary);
}
.bd-addon-list .bd-addon-card.settings-open {
max-height: 800px;
overflow-y: auto;
.bd-addon-list.bd-grid-view .bd-addon-card {
margin-bottom: 0;
}
.bd-addon-list .bd-addon-header {
@ -49,11 +58,16 @@
overflow: hidden;
}
.bd-description-wrap {
flex: 1;
}
.bd-addon-list .bd-description {
word-break: break-word;
margin-bottom: 5px;
padding: 5px 0;
overflow-y: auto;
max-height: 175px;
font-size: 14px;
line-height: 18px;
-webkit-line-clamp: 3;
@ -161,6 +175,7 @@
.bd-addon-modal-settings {
/* padding: 16px; */
padding: 0 16px 16px 16px;
}
.bd-addon-modal-footer .bd-button {
@ -177,4 +192,30 @@
.bd-addon-modal-footer .bd-button:active {
background-color: rgb(50, 104, 183);
}
.bd-addon-views {
display: flex;
margin-left: 10px;
}
.bd-addon-views .bd-view-button {
background-color: transparent;
padding: 3px 4px;
}
.bd-addon-views .bd-view-button:hover {
background-color: var(--background-modifier-selected);
}
.bd-addon-views .bd-view-button:active {
background-color: var(--background-modifier-accent);
}
.bd-addon-views .bd-view-button.selected {
background-color: #3e82e5;
}
.bd-addon-views .bd-view-button + .bd-view-button {
margin-left: 5px;
}

View File

@ -2,10 +2,18 @@
opacity: 0.6;
}
.bd-social-logo path {
fill: white;
}
.bd-social-link:hover .bd-social-logo {
opacity: 1;
}
.bd-social-link:hover .bd-social-logo path:first-of-type {
fill: #3e82e5;
}
.standardSidebarView-3F1I7i .bd-versioninfo-wrapper {
bottom: 0;
left: 0;

View File

@ -48,6 +48,7 @@
background: var(--background-secondary);
color: #fff;
flex: 1;
overflow: hidden;
}
.floating-window-titlebar .title {
@ -95,13 +96,16 @@
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
overflow: hidden;
}
.floating-window .editor-wrapper {
flex: 1;
overflow: hidden;
}
.floating-window .ace_editor {
.floating-window .editor {
height: auto;
flex: 1;
}

View File

@ -0,0 +1,19 @@
import {React, WebpackModules} from "modules";
const EmptyImageClasses = WebpackModules.getByProps("emptyImage") || {};
const MarkdownParser = WebpackModules.getByProps("markdownToReact");
export default class EmptyImage extends React.Component {
render() {
return <div className={`bd-empty-image-container ${EmptyImageClasses.emptyContainer}` + (this.props.className ? ` ${this.props.className}` : "")}>
<div className={`bd-empty-image ${EmptyImageClasses.emptyImage}`}></div>
<div className={`bd-empty-image-header ${EmptyImageClasses.emptyHeader}`}>
{this.props.title || "You don't have anything!"}
</div>
<div className={`bd-empty-image-message`}>
{MarkdownParser.markdownToReact(this.props.message || "You should probably get something.")}
</div>
{this.props.children}
</div>;
}
}

View File

@ -5,7 +5,6 @@ import Editor from "./editor";
import Refresh from "../icons/reload";
import Save from "../icons/save";
import Edit from "../icons/edit";
import Cog from "../icons/cog";
import Detach from "../icons/detach";
export default class CssEditor extends React.Component {
@ -27,7 +26,6 @@ export default class CssEditor extends React.Component {
{label: React.createElement(Refresh, {size: "18px"}), tooltip: Strings.CustomCSS.update, onClick: this.updateCss},
{label: React.createElement(Save, {size: "18px"}), tooltip: Strings.CustomCSS.save, onClick: this.saveCss},
{label: React.createElement(Edit, {size: "18px"}), tooltip: Strings.CustomCSS.openNative, onClick: this.openNative},
{label: React.createElement(Cog, {size: "18px"}), tooltip: Strings.CustomCSS.settings, onClick: "showSettings"},
{label: Strings.Collections.settings.customcss.liveUpdate.name, type: "checkbox", onChange: this.toggleLiveUpdate, checked: Settings.get("settings", "customcss", "liveUpdate"), side: "right"}
];
if (this.openDetached) this.controls.push({label: React.createElement(Detach, {size: "18px"}), tooltip: Strings.CustomCSS.openDetached, onClick: this.openDetached, side: "right"});

View File

@ -1,4 +1,4 @@
import {React, WebpackModules} from "modules";
import {React, WebpackModules, DiscordModules} from "modules";
import Checkbox from "./checkbox";
@ -12,11 +12,6 @@ export default class CodeEditor extends React.Component {
constructor(props) {
super(props);
for (const control of this.props.controls) {
if (control.type == "checkbox") continue;
if (control.onClick == "showSettings") control.onClick = this.showSettings.bind(this);
}
this.props.theme = this.props.theme.toLowerCase().replace(/ /g, "_");
if (!themes.includes(this.props.theme)) this.props.theme = CodeEditor.defaultProps.theme;
@ -24,6 +19,7 @@ export default class CodeEditor extends React.Component {
this.props.language = this.props.language.toLowerCase().replace(/ /g, "_");
if (!languages.includes(this.props.language)) this.props.language = CodeEditor.defaultProps.language;
this.bindings = [];
this.onChange = this.onChange.bind(this);
}
@ -40,47 +36,29 @@ export default class CodeEditor extends React.Component {
static get themes() {return themes;}
componentDidMount() {
this.editor = ace.edit(this.props.id);
this.editor = window.monaco.editor.create(document.getElementById(this.props.id), {
value: this.props.value,
language: this.props.language,
theme: DiscordModules.UserSettingsStore.theme == "light" ? "vs" : "vs-dark"
});
// Add id to the ace menu container
const originalShow = this.editor.keyBinding.$defaultHandler.commands.showSettingsMenu.exec;
this.editor.keyBinding.$defaultHandler.commands.showSettingsMenu.exec = function() {
originalShow.apply(this, arguments);
const observer = new MutationObserver(mutations => {
for (const mutation of mutations) {
if (!mutation.addedNodes.length || !(mutation.addedNodes[0] instanceof Element)) continue;
const node = mutation.addedNodes[0];
if (node.parentElement !== document.body || !node.querySelector("#ace_settingsmenu")) continue;
node.id = "ace_settingsmenu_container";
observer.disconnect();
}
});
observer.observe(document.body, {childList: true});
};
const theme = this.props.theme == CodeEditor.defaultProps.theme ? this.props.theme.split("-")[1] : this.props.theme;
this.editor.setTheme(`ace/theme/${theme}`);
this.editor.session.setMode(`ace/mode/${this.props.language}`);
this.editor.setShowPrintMargin(false);
this.editor.setFontSize(this.props.fontSize);
this.editor.on("change", this.onChange);
this.bindings.push(this.editor.onDidChangeModelContent(this.onChange));
}
componentWillUnmount() {
this.editor.destroy();
for (const binding of this.bindings) binding.dispose();
this.editor.dispose();
}
get value() {return this.editor.session.getValue();}
set value(newValue) {
this.editor.setValue(newValue);
}
get value() {return this.editor.getValue();}
set value(newValue) {this.editor.setValue(newValue);}
onChange() {
if (this.props.onChange) this.props.onChange(this.value);
}
showSettings() {return this.editor.keyBinding.$defaultHandler.commands.showSettingsMenu.exec(this.editor);}
resize() {return this.editor.resize();}
resize() {return this.editor.layout();}
buildControl(control) {
if (control.type == "checkbox") return this.makeCheckbox(control);
@ -100,7 +78,7 @@ export default class CodeEditor extends React.Component {
}
render() {
if (this.editor && this.editor.resize) this.editor.resize();
if (this.editor && this.editor.resize) this.editor.layout();
const controlsLeft = this.props.controls.filter(c => c.side != "right").map(this.buildControl.bind(this));
const controlsRight = this.props.controls.filter(c => c.side == "right").map(this.buildControl.bind(this));
@ -115,7 +93,7 @@ export default class CodeEditor extends React.Component {
</div>
</div>
<div className="editor-wrapper">
<div id={this.props.id} className={"editor " + this.props.theme}>{this.props.value}</div>
<div id={this.props.id} className={"editor " + this.props.theme}></div>
</div>
</div>;
}

View File

@ -2,17 +2,9 @@ import {React} from "modules";
export default class BDLogo extends React.Component {
render() {
return <svg className={"bd-logo " + this.props.className} height="100%" width={this.props.size || "16px"} viewBox="0 0 2000 2000" style={{fillRule: "evenodd", clipRule: "evenodd", strokeLinecap: "round", strokeLinejoin: "round"}}>
<metadata />
<defs>
<filter id="shadow1"><feDropShadow dx="20" dy="0" stdDeviation="20" floodColor="rgba(0,0,0,0.35)" /></filter>
<filter id="shadow2"><feDropShadow dx="15" dy="0" stdDeviation="20" floodColor="rgba(255,255,255,0.15)" /></filter>
<filter id="shadow3"><feDropShadow dx="10" dy="0" stdDeviation="20" floodColor="rgba(0,0,0,0.35)" /></filter>
</defs>
return <svg className={"bd-logo " + this.props.className} height="100%" width={this.props.size || "16px"} viewBox="0 0 2000 2000">
<g>
<path style={{filter: "url(#shadow3)"}} fill="#171717" opacity="1" d="M1195.44+135.442L1195.44+135.442L997.6+136.442C1024.2+149.742+1170.34+163.542+1193.64+179.742C1264.34+228.842+1319.74+291.242+1358.24+365.042C1398.14+441.642+1419.74+530.642+1422.54+629.642L1422.54+630.842L1422.54+632.042C1422.54+773.142+1422.54+1228.14+1422.54+1369.14L1422.54+1370.34L1422.54+1371.54C1419.84+1470.54+1398.24+1559.54+1358.24+1636.14C1319.74+1709.94+1264.44+1772.34+1193.64+1821.44C1171.04+1837.14+1025.7+1850.54+1000+1863.54L1193.54+1864.54C1539.74+1866.44+1864.54+1693.34+1864.54+1296.64L1864.54+716.942C1866.44+312.442+1541.64+135.442+1195.44+135.442Z" />
<path style={{filter: "url(#shadow2)"}} fill="#3E82E5" opacity="1" d="M1695.54+631.442C1685.84+278.042+1409.34+135.442+1052.94+135.442L361.74+136.442L803.74+490.442L1060.74+490.442C1335.24+490.442+1335.24+835.342+1060.74+835.342L1060.74+1164.84C1150.22+1164.84+1210.53+1201.48+1241.68+1250.87C1306.07+1353+1245.76+1509.64+1060.74+1509.64L361.74+1863.54L1052.94+1864.54C1409.24+1864.54+1685.74+1721.94+1695.54+1368.54C1695.54+1205.94+1651.04+1084.44+1572.64+999.942C1651.04+915.542+1695.54+794.042+1695.54+631.442Z" />
<path style={{filter: "url(#shadow1)"}} fill="#FFFFFF" opacity="1" d="M1469.25+631.442C1459.55+278.042+1183.05+135.442+826.65+135.442L135.45+135.442L135.45+1004C135.45+1004+135.427+1255.21+355.626+1255.21C575.825+1255.21+575.848+1004+575.848+1004L577.45+490.442L834.45+490.442C1108.95+490.442+1108.95+835.342+834.45+835.342L664.65+835.342L664.65+1164.84L834.45+1164.84C923.932+1164.84+984.244+1201.48+1015.39+1250.87C1079.78+1353+1019.47+1509.64+834.45+1509.64L135.45+1509.64L135.45+1864.54L826.65+1864.54C1182.95+1864.54+1459.45+1721.94+1469.25+1368.54C1469.25+1205.94+1424.75+1084.44+1346.35+999.942C1424.75+915.542+1469.25+794.042+1469.25+631.442Z" />
<path fill="#3E82E5" d="M1402.2,631.7c-9.7-353.4-286.2-496-642.6-496H68.4v714.1l442,398V490.7h257c274.5,0,274.5,344.9,0,344.9H597.6v329.5h169.8c274.5,0,274.5,344.8,0,344.8h-699v354.9h691.2c356.3,0,632.8-142.6,642.6-496c0-162.6-44.5-284.1-122.9-368.6C1357.7,915.8,1402.2,794.3,1402.2,631.7z"/><path fill="#FFFFFF" d="M1262.5,135.2L1262.5,135.2l-76.8,0c26.6,13.3,51.7,28.1,75,44.3c70.7,49.1,126.1,111.5,164.6,185.3c39.9,76.6,61.5,165.6,64.3,264.6l0,1.2v1.2c0,141.1,0,596.1,0,737.1v1.2l0,1.2c-2.7,99-24.3,188-64.3,264.6c-38.5,73.8-93.8,136.2-164.6,185.3c-22.6,15.7-46.9,30.1-72.6,43.1h72.5c346.2,1.9,671-171.2,671-567.9V716.7C1933.5,312.2,1608.7,135.2,1262.5,135.2z"/>
</g>
</svg>;
}

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

@ -0,0 +1,11 @@
import {React} from "modules";
export default class Grid extends React.Component {
render() {
const size = this.props.size || "20px";
return <svg viewBox="2 2 20 20" fill="#FFFFFF" style={{width: size, height: size}}>
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M4 11h5V5H4v6zm0 7h5v-6H4v6zm6 0h5v-6h-5v6zm6 0h5v-6h-5v6zm-6-7h5V5h-5v6zm6-6v6h5V5h-5z"/>
</svg>;
}
}

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

@ -0,0 +1,11 @@
import {React} from "modules";
export default class List extends React.Component {
render() {
const size = this.props.size || "20px";
return <svg viewBox="2 2 20 20" fill="#FFFFFF" style={{width: size, height: size}}>
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M4 18h17v-6H4v6zM4 5v6h17V5H4z"/>
</svg>;
}
}

View File

@ -3,7 +3,6 @@ import {React, Strings} from "modules";
import Editor from "../customcss/editor";
import Save from "../icons/save";
import Edit from "../icons/edit";
import Cog from "../icons/cog";
export default class AddonEditor extends React.Component {
@ -18,8 +17,7 @@ export default class AddonEditor extends React.Component {
this.controls = [
{label: React.createElement(Save, {size: "18px"}), tooltip: Strings.CustomCSS.save, onClick: this.save},
{label: React.createElement(Edit, {size: "18px"}), tooltip: Strings.CustomCSS.openNative, onClick: this.openNative},
{label: React.createElement(Cog, {size: "18px"}), tooltip: Strings.CustomCSS.settings, onClick: "showSettings"}
{label: React.createElement(Edit, {size: "18px"}), tooltip: Strings.CustomCSS.openNative, onClick: this.openNative}
];
}

View File

@ -2,7 +2,7 @@ import {React, WebpackModules, Strings} from "modules";
import Modals from "../modals";
import SettingsTitle from "../settings/title";
import ServerCard from "./card";
import EmptyResults from "./noresults";
import EmptyResults from "../blankslates/noresults";
import Connection from "../../structs/psconnection";
import Search from "../settings/components/search";
import Previous from "../icons/previous";

View File

@ -22,6 +22,7 @@ const LinkIcons = {
};
const Tooltip = WebpackModules.getByDisplayName("Tooltip");
const MarkdownParser = WebpackModules.getByProps("markdownToReact");
export default class AddonCard extends React.Component {
@ -141,7 +142,7 @@ export default class AddonCard extends React.Component {
<span className="bd-title">{this.buildTitle(name, version, author)}</span>
<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>
<div className="bd-description-wrap scroller-wrap fade"><div className="bd-description scroller">{MarkdownParser.markdownToReact(description)}</div></div>
{this.footer}
</div>;
}

View File

@ -1,4 +1,4 @@
import {React, Settings, Strings, Events, Logger} from "modules";
import {React, Settings, Strings, Events, Logger, WebpackModules} from "modules";
import Modals from "../modals";
import SettingsTitle from "./title";
@ -8,15 +8,25 @@ import Dropdown from "./components/dropdown";
import Search from "./components/search";
import ErrorBoundary from "../errorboundary";
import ListIcon from "../icons/list";
import GridIcon from "../icons/grid";
import NoResults from "../blankslates/noresults";
import EmptyImage from "../blankslates/emptyimage";
const Tooltip = WebpackModules.getByDisplayName("Tooltip");
export default class AddonList extends React.Component {
constructor(props) {
super(props);
this.state = {sort: "name", ascending: true, query: ""};
this.state = {sort: "name", ascending: true, query: "", view: "list"};
this.sort = this.sort.bind(this);
this.reverse = this.reverse.bind(this);
this.search = this.search.bind(this);
this.update = this.update.bind(this);
this.listView = this.listView.bind(this);
this.gridView = this.gridView.bind(this);
this.openFolder = this.openFolder.bind(this);
}
componentDidMount() {
@ -33,6 +43,14 @@ export default class AddonList extends React.Component {
this.forceUpdate();
}
listView() {
this.setState({view: "list"});
}
gridView() {
this.setState({view: "grid"});
}
reload() {
if (this.props.refreshList) this.props.refreshList();
this.forceUpdate();
@ -50,6 +68,12 @@ export default class AddonList extends React.Component {
this.setState({query: event.target.value.toLocaleLowerCase()});
}
openFolder() {
const shell = require("electron").shell;
const open = shell.openItem || shell.openPath;
open(this.props.folder);
}
get sortOptions() {
return [
{label: Strings.Addons.name, value: "name"},
@ -67,17 +91,25 @@ export default class AddonList extends React.Component {
];
}
get emptyImage() {
return <EmptyImage title={Strings.Addons.blankSlateHeader.format({type: this.props.title})} message={Strings.Addons.blankSlateMessage.format({link: "https://betterdiscordlibrary.com/themes", type: this.props.title}).toString()}>
<button className="bd-button" onClick={this.openFolder}>{Strings.Addons.openFolder.format({type: this.props.title})}</button>
</EmptyImage>;
}
makeControlButton(title, children, action, selected = false) {
return <Tooltip color="black" position="top" text={title}>
{(props) => {
return <button {...props} className={"bd-button bd-view-button" + (selected ? " selected" : "")} onClick={action}>{children}</button>;
}}
</Tooltip>;
}
render() {
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: () => {
const shell = require("electron").shell;
const open = shell.openItem || shell.openPath;
open(folder);
}} : null;
const sortedAddons = addonList.sort((a, b) => {
const button = folder ? {title: Strings.Addons.openFolder.format({type: title}), onClick: this.openFolder} : null;
let sortedAddons = addonList.sort((a, b) => {
const first = a[this.state.sort];
const second = b[this.state.sort];
if (typeof(first) == "string") return first.toLocaleLowerCase().localeCompare(second.toLocaleLowerCase());
@ -86,34 +118,44 @@ export default class AddonList extends React.Component {
return 0;
});
if (!this.state.ascending) sortedAddons.reverse();
if (this.state.query) {
sortedAddons = sortedAddons.filter(addon => {
let matches = addon.name.toLocaleLowerCase().includes(this.state.query);
matches = matches || addon.author.toLocaleLowerCase().includes(this.state.query);
matches = matches || addon.description.toLocaleLowerCase().includes(this.state.query);
if (!matches) return false;
return true;
});
}
return [
<SettingsTitle key="title" text={title} button={button} otherChildren={showReloadIcon && <ReloadIcon className="bd-reload" onClick={this.reload.bind(this)} />} />,
<div className="bd-controls bd-addon-controls">
<div className={"bd-controls bd-addon-controls"}>
<Search onChange={this.search} placeholder={`${Strings.Addons.search.format({type: this.props.title})}...`} />
<div className="bd-addon-dropdowns">
<div className="bd-select-wrapper">
<label className="bd-label">{Strings.Sorting.sortBy}:</label>
<Dropdown options={this.sortOptions} onChange={this.sort} style="transparent" />
<div className="bd-controls-advanced">
<div className="bd-addon-dropdowns">
<div className="bd-select-wrapper">
<label className="bd-label">{Strings.Sorting.sortBy}:</label>
<Dropdown options={this.sortOptions} onChange={this.sort} style="transparent" />
</div>
<div className="bd-select-wrapper">
<label className="bd-label">{Strings.Sorting.order}:</label>
<Dropdown options={this.directions} onChange={this.reverse} style="transparent" />
</div>
</div>
<div className="bd-select-wrapper">
<label className="bd-label">{Strings.Sorting.order}:</label>
<Dropdown options={this.directions} onChange={this.reverse} style="transparent" />
<div className="bd-addon-views">
{this.makeControlButton("List View", <ListIcon />, this.listView, this.state.view === "list")}
{this.makeControlButton("Grid View", <GridIcon />, this.gridView, this.state.view === "grid")}
</div>
</div>
</div>,
<div key="addonList" className={"bd-addon-list"}>
<div key="addonList" className={"bd-addon-list" + (this.state.view == "grid" ? " bd-grid-view" : "")}>
{sortedAddons.map(addon => {
if (this.state.query) {
let matches = addon.name.toLocaleLowerCase().includes(this.state.query);
matches = matches || addon.author.toLocaleLowerCase().includes(this.state.query);
matches = matches || addon.description.toLocaleLowerCase().includes(this.state.query);
if (!matches) return null;
}
const hasSettings = addon.instance && typeof(addon.instance.getSettingsPanel) === "function";
const getSettings = hasSettings && addon.instance.getSettingsPanel.bind(addon.instance);
return <ErrorBoundary><AddonCard editAddon={this.editAddon.bind(this, addon.id)} deleteAddon={this.deleteAddon.bind(this, addon.id)} showReloadIcon={showReloadIcon} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} hasSettings={hasSettings} getSettingsPanel={getSettings} /></ErrorBoundary>;
})}
{this.props.addonList.length === 0 && this.emptyImage}
{this.state.query && sortedAddons.length == 0 && this.props.addonList.length !== 0 && <NoResults />}
</div>
];
}