From f1e0cc88cb0d7627e1a84cf7d8adb3d9348e105a Mon Sep 17 00:00:00 2001 From: Zack Rauen Date: Wed, 4 Nov 2020 18:04:44 -0500 Subject: [PATCH] Adds some more UX features --- src/data/strings.js | 2 +- src/structs/markdown.js | 23 +++++++++++++ src/styles/index.css | 8 +++++ src/styles/ui/addonlist.css | 4 +++ src/ui/blankslates/emptyimage.jsx | 3 +- src/ui/customcss/editor.jsx | 27 ++++++++++------ src/ui/settings/addoncard.jsx | 3 +- src/ui/settings/addonlist.jsx | 43 ++++++++++++++++--------- src/ui/settings/components/dropdown.jsx | 2 +- 9 files changed, 86 insertions(+), 29 deletions(-) create mode 100644 src/structs/markdown.js diff --git a/src/data/strings.js b/src/data/strings.js index 3534d930..0f91fa6e 100644 --- a/src/data/strings.js +++ b/src/data/strings.js @@ -231,7 +231,7 @@ export default { compileError: "Could not be compiled.", wasUnloaded: "{{name}} was unloaded.", 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: { confirmationText: "You have unsaved changes to your Custom CSS. Closing this window will lose all those changes.", diff --git a/src/structs/markdown.js b/src/structs/markdown.js new file mode 100644 index 00000000..1217a548 --- /dev/null +++ b/src/structs/markdown.js @@ -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")); + } +} \ No newline at end of file diff --git a/src/styles/index.css b/src/styles/index.css index 0680b82f..797ecd09 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -26,4 +26,12 @@ .bd-changelog-modal video { width: 100%; border-radius: 20px; +} + +.bd-link { + text-decoration: none; +} + +.bd-link:hover { + text-decoration: underline; } \ No newline at end of file diff --git a/src/styles/ui/addonlist.css b/src/styles/ui/addonlist.css index 6587a307..68fec2e6 100644 --- a/src/styles/ui/addonlist.css +++ b/src/styles/ui/addonlist.css @@ -74,6 +74,10 @@ color: var(--header-secondary); } +.bd-description em { + font-style: italic; +} + .bd-addon-list .scroller::-webkit-scrollbar-track-piece, .bd-addon-list .scroller::-webkit-scrollbar-thumb { border-radius: 0 !important; diff --git a/src/ui/blankslates/emptyimage.jsx b/src/ui/blankslates/emptyimage.jsx index eed9de71..7cc09b00 100644 --- a/src/ui/blankslates/emptyimage.jsx +++ b/src/ui/blankslates/emptyimage.jsx @@ -1,4 +1,5 @@ import {React, WebpackModules} from "modules"; +import SimpleMarkdown from "../../structs/markdown"; 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.message || "You should probably get something."} + {SimpleMarkdown.parseToReact(this.props.message || "You should probably get something.")}
{this.props.children} ; diff --git a/src/ui/customcss/editor.jsx b/src/ui/customcss/editor.jsx index 71737647..7b66ace4 100644 --- a/src/ui/customcss/editor.jsx +++ b/src/ui/customcss/editor.jsx @@ -5,7 +5,6 @@ import Checkbox from "./checkbox"; 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 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 { static get defaultId() {return "bd-editor";} @@ -13,28 +12,25 @@ export default class CodeEditor extends React.Component { constructor(props) { super(props); - this.props.theme = this.props.theme.toLowerCase().replace(/ /g, "_"); - if (!themes.includes(this.props.theme)) this.props.theme = CodeEditor.defaultProps.theme; + this.props.theme = DiscordModules.UserSettingsStore && DiscordModules.UserSettingsStore.theme === "light" ? "vs" : "vs-dark"; this.props.language = this.props.language.toLowerCase().replace(/ /g, "_"); if (!languages.includes(this.props.language)) this.props.language = CodeEditor.defaultProps.language; this.bindings = []; + this.resize = this.resize.bind(this); this.onChange = this.onChange.bind(this); + this.onThemeChange = this.onThemeChange.bind(this); } static get defaultProps() { return { controls: [], - theme: "bd-monokai", language: "css", - id: this.defaultId, - fontSize: 14 + id: this.defaultId }; } - static get themes() {return themes;} - componentDidMount() { this.editor = window.monaco.editor.create(document.getElementById(this.props.id), { value: this.props.value, @@ -42,14 +38,25 @@ export default class CodeEditor extends React.Component { 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)); } componentWillUnmount() { + window.removeEventListener("resize", this.resize); + if (DiscordModules.UserSettingsStore) DiscordModules.UserSettingsStore.removeChangeListener(this.onThemeChange); for (const binding of this.bindings) binding.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();} 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);} - resize() {return this.editor.layout();} + resize() {this.editor.layout();} buildControl(control) { if (control.type == "checkbox") return this.makeCheckbox(control); @@ -78,7 +85,7 @@ export default class CodeEditor extends React.Component { } 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 controlsRight = this.props.controls.filter(c => c.side == "right").map(this.buildControl.bind(this)); diff --git a/src/ui/settings/addoncard.jsx b/src/ui/settings/addoncard.jsx index 6b051b65..2ce49553 100644 --- a/src/ui/settings/addoncard.jsx +++ b/src/ui/settings/addoncard.jsx @@ -1,4 +1,5 @@ import {React, Logger, Strings, WebpackModules, DiscordModules} from "modules"; +import SimpleMarkdown from "../../structs/markdown"; import ReloadIcon from "../icons/reload"; import EditIcon from "../icons/edit"; import DeleteIcon from "../icons/delete"; @@ -141,7 +142,7 @@ export default class AddonCard extends React.Component { {this.buildTitle(name, version, author)} -
{description}
+
{SimpleMarkdown.parseToReact(description)}
{this.footer} ; } diff --git a/src/ui/settings/addonlist.jsx b/src/ui/settings/addonlist.jsx index ad6fb0d2..1df6bed1 100644 --- a/src/ui/settings/addonlist.jsx +++ b/src/ui/settings/addonlist.jsx @@ -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 SettingsTitle from "./title"; @@ -12,7 +12,6 @@ import ListIcon from "../icons/list"; import GridIcon from "../icons/grid"; import NoResults from "../blankslates/noresults"; import EmptyImage from "../blankslates/emptyimage"; -import FormattableString from "../../structs/string"; const Tooltip = WebpackModules.getByDisplayName("Tooltip"); @@ -20,7 +19,7 @@ export default class AddonList extends React.Component { constructor(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.reverse = this.reverse.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); } + 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() { this.forceUpdate(); } - listView() { - this.setState({view: "list"}); - } - - gridView() { - this.setState({view: "grid"}); - } - reload() { if (this.props.refreshList) this.props.refreshList(); this.forceUpdate(); } + listView() {this.changeView("list");} + gridView() {this.changeView("grid");} + changeView(view) { + this.onControlChange("view", view); + this.setState({view}); + } + reverse(value) { + this.onControlChange("ascending", value); this.setState({ascending: value}); } sort(value) { + this.onControlChange("sort", value); this.setState({sort: value}); } @@ -93,8 +107,7 @@ export default class AddonList extends React.Component { } get emptyImage() { - let message = Strings.Addons.blankSlateMessage.format({type: this.props.title}); - message = FormattableString.prototype.replaceLink.call(message, label => {label}); + const message = Strings.Addons.blankSlateMessage.format({link: `https://betterdiscordlibrary.com/${this.props.title.toLowerCase()}`, type: this.props.title}).toString(); return ; @@ -149,11 +162,11 @@ export default class AddonList extends React.Component {
- +
- +
diff --git a/src/ui/settings/components/dropdown.jsx b/src/ui/settings/components/dropdown.jsx index cc8e99f4..2762bbda 100644 --- a/src/ui/settings/components/dropdown.jsx +++ b/src/ui/settings/components/dropdown.jsx @@ -4,7 +4,7 @@ import Arrow from "../../icons/downarrow"; export default class Select extends React.Component { constructor(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.onChange = this.onChange.bind(this); this.showMenu = this.showMenu.bind(this);