Adds some more UX features
This commit is contained in:
parent
e15d1d0995
commit
f1e0cc88cb
|
@ -231,7 +231,7 @@ export default {
|
||||||
compileError: "Could not be compiled.",
|
compileError: "Could not be compiled.",
|
||||||
wasUnloaded: "{{name}} was unloaded.",
|
wasUnloaded: "{{name}} was unloaded.",
|
||||||
blankSlateHeader: "You don't have any {{type}}!",
|
blankSlateHeader: "You don't have any {{type}}!",
|
||||||
blankSlateMessage: "Grab some from [this website] and add them to your {{type}} folder."
|
blankSlateMessage: "Grab some from [this website]({{link}}) and add them to your {{type}} folder."
|
||||||
},
|
},
|
||||||
CustomCSS: {
|
CustomCSS: {
|
||||||
confirmationText: "You have unsaved changes to your Custom CSS. Closing this window will lose all those changes.",
|
confirmationText: "You have unsaved changes to your Custom CSS. Closing this window will lose all those changes.",
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import {DiscordModules, Utilities} from "modules";
|
||||||
|
|
||||||
|
export default class SimpleMarkdownExt {
|
||||||
|
static parseToReact(str) {
|
||||||
|
if (!this._parser) this._initialize();
|
||||||
|
return this._renderer(this._parse(str, {inline: true}));
|
||||||
|
}
|
||||||
|
|
||||||
|
static _initialize() {
|
||||||
|
const SMD = DiscordModules.SimpleMarkdown;
|
||||||
|
const originalLink = SMD.defaultRules.link.react;
|
||||||
|
const newRules = Utilities.extend({}, SMD.defaultRules, {link: {react: function() {
|
||||||
|
const original = Reflect.apply(originalLink, undefined, arguments);
|
||||||
|
original.props.className = "bd-link";
|
||||||
|
original.props.target = "_blank";
|
||||||
|
original.props.rel = "noopener noreferrer";
|
||||||
|
return original;
|
||||||
|
}}});
|
||||||
|
|
||||||
|
this._parse = SMD.parserFor(newRules);
|
||||||
|
this._renderer = SMD.reactFor(SMD.ruleOutput(newRules, "react"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,4 +26,12 @@
|
||||||
.bd-changelog-modal video {
|
.bd-changelog-modal video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-link {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
|
@ -74,6 +74,10 @@
|
||||||
color: var(--header-secondary);
|
color: var(--header-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bd-description em {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.bd-addon-list .scroller::-webkit-scrollbar-track-piece,
|
.bd-addon-list .scroller::-webkit-scrollbar-track-piece,
|
||||||
.bd-addon-list .scroller::-webkit-scrollbar-thumb {
|
.bd-addon-list .scroller::-webkit-scrollbar-thumb {
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {React, WebpackModules} from "modules";
|
import {React, WebpackModules} from "modules";
|
||||||
|
import SimpleMarkdown from "../../structs/markdown";
|
||||||
|
|
||||||
const EmptyImageClasses = WebpackModules.getByProps("emptyImage") || {};
|
const EmptyImageClasses = WebpackModules.getByProps("emptyImage") || {};
|
||||||
|
|
||||||
|
@ -10,7 +11,7 @@ export default class EmptyImage extends React.Component {
|
||||||
{this.props.title || "You don't have anything!"}
|
{this.props.title || "You don't have anything!"}
|
||||||
</div>
|
</div>
|
||||||
<div className={`bd-empty-image-message`}>
|
<div className={`bd-empty-image-message`}>
|
||||||
{this.props.message || "You should probably get something."}
|
{SimpleMarkdown.parseToReact(this.props.message || "You should probably get something.")}
|
||||||
</div>
|
</div>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -5,7 +5,6 @@ import Checkbox from "./checkbox";
|
||||||
const Tooltip = WebpackModules.getByDisplayName("Tooltip");
|
const Tooltip = WebpackModules.getByDisplayName("Tooltip");
|
||||||
|
|
||||||
const languages = ["abap", "abc", "actionscript", "ada", "apache_conf", "asciidoc", "assembly_x86", "autohotkey", "batchfile", "bro", "c_cpp", "c9search", "cirru", "clojure", "cobol", "coffee", "coldfusion", "csharp", "csound_document", "csound_orchestra", "csound_score", "css", "curly", "d", "dart", "diff", "dockerfile", "dot", "drools", "dummy", "dummysyntax", "eiffel", "ejs", "elixir", "elm", "erlang", "forth", "fortran", "ftl", "gcode", "gherkin", "gitignore", "glsl", "gobstones", "golang", "graphqlschema", "groovy", "haml", "handlebars", "haskell", "haskell_cabal", "haxe", "hjson", "html", "html_elixir", "html_ruby", "ini", "io", "jack", "jade", "java", "javascript", "json", "jsoniq", "jsp", "jssm", "jsx", "julia", "kotlin", "latex", "less", "liquid", "lisp", "livescript", "logiql", "lsl", "lua", "luapage", "lucene", "makefile", "markdown", "mask", "matlab", "maze", "mel", "mushcode", "mysql", "nix", "nsis", "objectivec", "ocaml", "pascal", "perl", "pgsql", "php", "pig", "powershell", "praat", "prolog", "properties", "protobuf", "python", "r", "razor", "rdoc", "red", "rhtml", "rst", "ruby", "rust", "sass", "scad", "scala", "scheme", "scss", "sh", "sjs", "smarty", "snippets", "soy_template", "space", "sql", "sqlserver", "stylus", "svg", "swift", "tcl", "tex", "text", "textile", "toml", "tsx", "twig", "typescript", "vala", "vbscript", "velocity", "verilog", "vhdl", "wollok", "xml", "xquery", "yaml", "django"];
|
const languages = ["abap", "abc", "actionscript", "ada", "apache_conf", "asciidoc", "assembly_x86", "autohotkey", "batchfile", "bro", "c_cpp", "c9search", "cirru", "clojure", "cobol", "coffee", "coldfusion", "csharp", "csound_document", "csound_orchestra", "csound_score", "css", "curly", "d", "dart", "diff", "dockerfile", "dot", "drools", "dummy", "dummysyntax", "eiffel", "ejs", "elixir", "elm", "erlang", "forth", "fortran", "ftl", "gcode", "gherkin", "gitignore", "glsl", "gobstones", "golang", "graphqlschema", "groovy", "haml", "handlebars", "haskell", "haskell_cabal", "haxe", "hjson", "html", "html_elixir", "html_ruby", "ini", "io", "jack", "jade", "java", "javascript", "json", "jsoniq", "jsp", "jssm", "jsx", "julia", "kotlin", "latex", "less", "liquid", "lisp", "livescript", "logiql", "lsl", "lua", "luapage", "lucene", "makefile", "markdown", "mask", "matlab", "maze", "mel", "mushcode", "mysql", "nix", "nsis", "objectivec", "ocaml", "pascal", "perl", "pgsql", "php", "pig", "powershell", "praat", "prolog", "properties", "protobuf", "python", "r", "razor", "rdoc", "red", "rhtml", "rst", "ruby", "rust", "sass", "scad", "scala", "scheme", "scss", "sh", "sjs", "smarty", "snippets", "soy_template", "space", "sql", "sqlserver", "stylus", "svg", "swift", "tcl", "tex", "text", "textile", "toml", "tsx", "twig", "typescript", "vala", "vbscript", "velocity", "verilog", "vhdl", "wollok", "xml", "xquery", "yaml", "django"];
|
||||||
const themes = ["chrome", "clouds", "crimson_editor", "dawn", "dreamweaver", "eclipse", "github", "iplastic", "solarized_light", "textmate", "tomorrow", "xcode", "kuroir", "katzenmilch", "sqlserver", "ambiance", "chaos", "clouds_midnight", "cobalt", "gruvbox", "gob", "idle_fingers", "kr_theme", "merbivore", "merbivore_soft", "mono_industrial", "monokai", "pastel_on_dark", "solarized_dark", "terminal", "tomorrow_night", "tomorrow_night_blue", "tomorrow_night_bright", "tomorrow_night_eighties", "twilight", "vibrant_ink"];
|
|
||||||
|
|
||||||
export default class CodeEditor extends React.Component {
|
export default class CodeEditor extends React.Component {
|
||||||
static get defaultId() {return "bd-editor";}
|
static get defaultId() {return "bd-editor";}
|
||||||
|
@ -13,28 +12,25 @@ export default class CodeEditor extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.props.theme = this.props.theme.toLowerCase().replace(/ /g, "_");
|
this.props.theme = DiscordModules.UserSettingsStore && DiscordModules.UserSettingsStore.theme === "light" ? "vs" : "vs-dark";
|
||||||
if (!themes.includes(this.props.theme)) this.props.theme = CodeEditor.defaultProps.theme;
|
|
||||||
|
|
||||||
this.props.language = this.props.language.toLowerCase().replace(/ /g, "_");
|
this.props.language = this.props.language.toLowerCase().replace(/ /g, "_");
|
||||||
if (!languages.includes(this.props.language)) this.props.language = CodeEditor.defaultProps.language;
|
if (!languages.includes(this.props.language)) this.props.language = CodeEditor.defaultProps.language;
|
||||||
|
|
||||||
this.bindings = [];
|
this.bindings = [];
|
||||||
|
this.resize = this.resize.bind(this);
|
||||||
this.onChange = this.onChange.bind(this);
|
this.onChange = this.onChange.bind(this);
|
||||||
|
this.onThemeChange = this.onThemeChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get defaultProps() {
|
static get defaultProps() {
|
||||||
return {
|
return {
|
||||||
controls: [],
|
controls: [],
|
||||||
theme: "bd-monokai",
|
|
||||||
language: "css",
|
language: "css",
|
||||||
id: this.defaultId,
|
id: this.defaultId
|
||||||
fontSize: 14
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static get themes() {return themes;}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.editor = window.monaco.editor.create(document.getElementById(this.props.id), {
|
this.editor = window.monaco.editor.create(document.getElementById(this.props.id), {
|
||||||
value: this.props.value,
|
value: this.props.value,
|
||||||
|
@ -42,14 +38,25 @@ export default class CodeEditor extends React.Component {
|
||||||
theme: DiscordModules.UserSettingsStore.theme == "light" ? "vs" : "vs-dark"
|
theme: DiscordModules.UserSettingsStore.theme == "light" ? "vs" : "vs-dark"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.addEventListener("resize", this.resize);
|
||||||
|
if (DiscordModules.UserSettingsStore) DiscordModules.UserSettingsStore.addChangeListener(this.onThemeChange);
|
||||||
this.bindings.push(this.editor.onDidChangeModelContent(this.onChange));
|
this.bindings.push(this.editor.onDidChangeModelContent(this.onChange));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener("resize", this.resize);
|
||||||
|
if (DiscordModules.UserSettingsStore) DiscordModules.UserSettingsStore.removeChangeListener(this.onThemeChange);
|
||||||
for (const binding of this.bindings) binding.dispose();
|
for (const binding of this.bindings) binding.dispose();
|
||||||
this.editor.dispose();
|
this.editor.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onThemeChange() {
|
||||||
|
const newTheme = DiscordModules.UserSettingsStore.theme === "light" ? "vs" : "vs-dark";
|
||||||
|
if (newTheme === this.props.theme) return;
|
||||||
|
this.props.theme = newTheme;
|
||||||
|
window.monaco.editor.setTheme(this.props.theme);
|
||||||
|
}
|
||||||
|
|
||||||
get value() {return this.editor.getValue();}
|
get value() {return this.editor.getValue();}
|
||||||
set value(newValue) {this.editor.setValue(newValue);}
|
set value(newValue) {this.editor.setValue(newValue);}
|
||||||
|
|
||||||
|
@ -58,7 +65,7 @@ export default class CodeEditor extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
showSettings() {return this.editor.keyBinding.$defaultHandler.commands.showSettingsMenu.exec(this.editor);}
|
showSettings() {return this.editor.keyBinding.$defaultHandler.commands.showSettingsMenu.exec(this.editor);}
|
||||||
resize() {return this.editor.layout();}
|
resize() {this.editor.layout();}
|
||||||
|
|
||||||
buildControl(control) {
|
buildControl(control) {
|
||||||
if (control.type == "checkbox") return this.makeCheckbox(control);
|
if (control.type == "checkbox") return this.makeCheckbox(control);
|
||||||
|
@ -78,7 +85,7 @@ export default class CodeEditor extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.editor && this.editor.resize) this.editor.layout();
|
if (this.editor && this.editor.layout) this.editor.layout();
|
||||||
|
|
||||||
const controlsLeft = this.props.controls.filter(c => c.side != "right").map(this.buildControl.bind(this));
|
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));
|
const controlsRight = this.props.controls.filter(c => c.side == "right").map(this.buildControl.bind(this));
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {React, Logger, Strings, WebpackModules, DiscordModules} from "modules";
|
import {React, Logger, Strings, WebpackModules, DiscordModules} from "modules";
|
||||||
|
import SimpleMarkdown from "../../structs/markdown";
|
||||||
import ReloadIcon from "../icons/reload";
|
import ReloadIcon from "../icons/reload";
|
||||||
import EditIcon from "../icons/edit";
|
import EditIcon from "../icons/edit";
|
||||||
import DeleteIcon from "../icons/delete";
|
import DeleteIcon from "../icons/delete";
|
||||||
|
@ -141,7 +142,7 @@ export default class AddonCard extends React.Component {
|
||||||
<span className="bd-title">{this.buildTitle(name, version, author)}</span>
|
<span className="bd-title">{this.buildTitle(name, version, author)}</span>
|
||||||
<Switch checked={this.props.enabled} onChange={this.onChange} />
|
<Switch checked={this.props.enabled} onChange={this.onChange} />
|
||||||
</div>
|
</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">{SimpleMarkdown.parseToReact(description)}</div></div>
|
||||||
{this.footer}
|
{this.footer}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {React, Settings, Strings, Events, Logger, WebpackModules} from "modules";
|
import {React, Settings, Strings, Events, Logger, WebpackModules, DataStore} from "modules";
|
||||||
|
|
||||||
import Modals from "../modals";
|
import Modals from "../modals";
|
||||||
import SettingsTitle from "./title";
|
import SettingsTitle from "./title";
|
||||||
|
@ -12,7 +12,6 @@ import ListIcon from "../icons/list";
|
||||||
import GridIcon from "../icons/grid";
|
import GridIcon from "../icons/grid";
|
||||||
import NoResults from "../blankslates/noresults";
|
import NoResults from "../blankslates/noresults";
|
||||||
import EmptyImage from "../blankslates/emptyimage";
|
import EmptyImage from "../blankslates/emptyimage";
|
||||||
import FormattableString from "../../structs/string";
|
|
||||||
|
|
||||||
const Tooltip = WebpackModules.getByDisplayName("Tooltip");
|
const Tooltip = WebpackModules.getByDisplayName("Tooltip");
|
||||||
|
|
||||||
|
@ -20,7 +19,7 @@ export default class AddonList extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {sort: "name", ascending: true, query: "", view: "list"};
|
this.state = {query: "", sort: this.getControlState("sort", "name"), ascending: this.getControlState("ascending", true), view: this.getControlState("view", "list")};
|
||||||
this.sort = this.sort.bind(this);
|
this.sort = this.sort.bind(this);
|
||||||
this.reverse = this.reverse.bind(this);
|
this.reverse = this.reverse.bind(this);
|
||||||
this.search = this.search.bind(this);
|
this.search = this.search.bind(this);
|
||||||
|
@ -40,28 +39,43 @@ export default class AddonList extends React.Component {
|
||||||
Events.off(`${this.props.prefix}-unloaded`, this.update);
|
Events.off(`${this.props.prefix}-unloaded`, this.update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onControlChange(control, value) {
|
||||||
|
const addonlistControls = DataStore.getBDData("addonlistControls") || {};
|
||||||
|
if (!addonlistControls[this.props.title.toLowerCase()]) addonlistControls[this.props.title.toLowerCase()] = {};
|
||||||
|
addonlistControls[this.props.title.toLowerCase()][control] = value;
|
||||||
|
DataStore.setBDData("addonlistControls", addonlistControls);
|
||||||
|
}
|
||||||
|
|
||||||
|
getControlState(control, defaultValue) {
|
||||||
|
const addonlistControls = DataStore.getBDData("addonlistControls") || {};
|
||||||
|
if (!addonlistControls[this.props.title.toLowerCase()]) return defaultValue;
|
||||||
|
if (!addonlistControls[this.props.title.toLowerCase()].hasOwnProperty(control)) return defaultValue;
|
||||||
|
return addonlistControls[this.props.title.toLowerCase()][control];
|
||||||
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
listView() {
|
|
||||||
this.setState({view: "list"});
|
|
||||||
}
|
|
||||||
|
|
||||||
gridView() {
|
|
||||||
this.setState({view: "grid"});
|
|
||||||
}
|
|
||||||
|
|
||||||
reload() {
|
reload() {
|
||||||
if (this.props.refreshList) this.props.refreshList();
|
if (this.props.refreshList) this.props.refreshList();
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listView() {this.changeView("list");}
|
||||||
|
gridView() {this.changeView("grid");}
|
||||||
|
changeView(view) {
|
||||||
|
this.onControlChange("view", view);
|
||||||
|
this.setState({view});
|
||||||
|
}
|
||||||
|
|
||||||
reverse(value) {
|
reverse(value) {
|
||||||
|
this.onControlChange("ascending", value);
|
||||||
this.setState({ascending: value});
|
this.setState({ascending: value});
|
||||||
}
|
}
|
||||||
|
|
||||||
sort(value) {
|
sort(value) {
|
||||||
|
this.onControlChange("sort", value);
|
||||||
this.setState({sort: value});
|
this.setState({sort: value});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,8 +107,7 @@ export default class AddonList extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
get emptyImage() {
|
get emptyImage() {
|
||||||
let message = Strings.Addons.blankSlateMessage.format({type: this.props.title});
|
const message = Strings.Addons.blankSlateMessage.format({link: `https://betterdiscordlibrary.com/${this.props.title.toLowerCase()}`, type: this.props.title}).toString();
|
||||||
message = FormattableString.prototype.replaceLink.call(message, label => <a className="anchor-3Z-8Bb anchorUnderlineOnHover-2ESHQB cta" href="https://betterdiscordlibrary.com/themes" target="_blank" rel="noopener noreferrer">{label}</a>);
|
|
||||||
return <EmptyImage title={Strings.Addons.blankSlateHeader.format({type: this.props.title})} message={message}>
|
return <EmptyImage title={Strings.Addons.blankSlateHeader.format({type: this.props.title})} message={message}>
|
||||||
<button className="bd-button" onClick={this.openFolder}>{Strings.Addons.openFolder.format({type: this.props.title})}</button>
|
<button className="bd-button" onClick={this.openFolder}>{Strings.Addons.openFolder.format({type: this.props.title})}</button>
|
||||||
</EmptyImage>;
|
</EmptyImage>;
|
||||||
|
@ -149,11 +162,11 @@ export default class AddonList extends React.Component {
|
||||||
<div className="bd-addon-dropdowns">
|
<div className="bd-addon-dropdowns">
|
||||||
<div className="bd-select-wrapper">
|
<div className="bd-select-wrapper">
|
||||||
<label className="bd-label">{Strings.Sorting.sortBy}:</label>
|
<label className="bd-label">{Strings.Sorting.sortBy}:</label>
|
||||||
<Dropdown options={this.sortOptions} onChange={this.sort} style="transparent" />
|
<Dropdown options={this.sortOptions} value={this.state.sort} onChange={this.sort} style="transparent" />
|
||||||
</div>
|
</div>
|
||||||
<div className="bd-select-wrapper">
|
<div className="bd-select-wrapper">
|
||||||
<label className="bd-label">{Strings.Sorting.order}:</label>
|
<label className="bd-label">{Strings.Sorting.order}:</label>
|
||||||
<Dropdown options={this.directions} onChange={this.reverse} style="transparent" />
|
<Dropdown options={this.directions} value={this.state.ascending} onChange={this.reverse} style="transparent" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bd-addon-views">
|
<div className="bd-addon-views">
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Arrow from "../../icons/downarrow";
|
||||||
export default class Select extends React.Component {
|
export default class Select extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {open: false, value: this.props.value || this.props.options[0].value};
|
this.state = {open: false, value: this.props.hasOwnProperty("value") ? this.props.value : this.props.options[0].value};
|
||||||
this.dropdown = React.createRef();
|
this.dropdown = React.createRef();
|
||||||
this.onChange = this.onChange.bind(this);
|
this.onChange = this.onChange.bind(this);
|
||||||
this.showMenu = this.showMenu.bind(this);
|
this.showMenu = this.showMenu.bind(this);
|
||||||
|
|
Loading…
Reference in New Issue