diff --git a/renderer/src/ui/customcss/csseditor.jsx b/renderer/src/ui/customcss/csseditor.jsx
index c9ddc2cf..d9126b4b 100644
--- a/renderer/src/ui/customcss/csseditor.jsx
+++ b/renderer/src/ui/customcss/csseditor.jsx
@@ -1,91 +1,65 @@
import {React, Settings, Events, Strings} from "modules";
import Editor from "./editor";
-// import Checkbox from "./checkbox";
import Refresh from "../icons/reload";
import Save from "../icons/save";
import Edit from "../icons/edit";
import Detach from "../icons/detach";
-export default class CssEditor extends React.Component {
+const {useState, useCallback, useEffect, forwardRef, useImperativeHandle, useRef} = React;
- constructor(props) {
- super(props);
- this.hasUnsavedChanges = false;
+export default forwardRef(function CssEditor({css, openNative, update, save, onChange: notifyParent, readOnly = false, id = "bd-customcss-editor", openDetached = false}, ref) {
+ const editorRef = useRef(null);
+ const [hasUnsavedChanges, setUnsaved] = useState(false);
- this.onChange = this.onChange.bind(this);
- this.toggleLiveUpdate = this.toggleLiveUpdate.bind(this);
- this.updateCss = this.updateCss.bind(this);
- this.saveCss = this.saveCss.bind(this);
- this.openDetached = this.props.openDetached ? this.openDetached.bind(this) : null;
- this.openNative = this.openNative.bind(this);
- this.updateEditor = this.updateEditor.bind(this);
+ const updateEditor = useCallback((newCSS) => {
+ editorRef.current.value = newCSS;
+ }, [editorRef]);
- this.controls = [
- {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: 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"});
- }
+ useImperativeHandle(ref, () => {
+ return {
+ resize() {editorRef.current.resize();},
+ showSettings() {editorRef.current.showSettings();},
+ get value() {return editorRef.current.getValue();},
+ set value(newValue) {editorRef.current.setValue(newValue);},
+ get hasUnsavedChanges() {return hasUnsavedChanges;}
+ };
+ }, []);
- componentDidMount() {
- Events.on("customcss-updated", this.updateEditor);
- }
+ useEffect(() => {
+ Events.on("customcss-updated", updateEditor);
+ return () => Events.off("customcss-updated", updateEditor);
+ });
- componentWillUnmount() {
- Events.off("customcss-updated", this.updateEditor);
- }
+ const toggleLiveUpdate = useCallback((checked) => Settings.set("settings", "customcss", "liveUpdate", checked), []);
+ const updateCss = useCallback((event, newCSS) => update?.(newCSS), []);
+ const popoutNative = useCallback(() => openNative?.(), []);
+ const popout = useCallback((event, currentCSS) => openDetached?.(currentCSS), []);
- updateEditor(newCSS) {
- if (!this.editor) return;
- this.editor.value = newCSS;
- }
+ const onChange = useCallback((newCSS) => {
+ notifyParent?.(newCSS);
+ setUnsaved(true);
+ }, []);
- get value() {return this.editor.session.getValue();}
- set value(newValue) {
- this.editor.setValue(newValue);
- }
+ const saveCss = useCallback((event, newCSS) => {
+ save?.(newCSS);
+ setUnsaved(false);
+ }, []);
+
- showSettings() {return this.editor.keyBinding.$defaultHandler.commands.showSettingsMenu.exec(this.editor);}
- resize() {return this.editor.resize();}
-
- setEditorRef(editor) {
- this.editor = editor;
- if (this.props.editorRef && typeof(this.props.editorRef.current) !== "undefined") this.props.editorRef.current = editor;
- else if (this.props.editorRef) this.props.editorRef = editor;
- }
-
- onChange() {
- this.hasUnsavedChanges = true;
- if (this.props.onChange) this.props.onChange(...arguments);
- }
-
- render() {
- return ;
- }
-
- toggleLiveUpdate(checked) {
- Settings.set("settings", "customcss", "liveUpdate", checked);
- }
-
- updateCss(event, newCss) {
- if (this.props.update) this.props.update(newCss);
- }
-
- saveCss(event, newCss) {
- this.hasUnsavedChanges = false;
- if (this.props.save) this.props.save(newCss);
- }
-
- openDetached(event, currentCSS) {
- if (!this.props.openDetached) return;
- this.props.openDetached(currentCSS);
- }
-
- openNative() {
- if (this.props.openNative) this.props.openNative();
- }
-}
\ No newline at end of file
+ return , tooltip: Strings.CustomCSS.update, onClick: updateCss},
+ {label: , tooltip: Strings.CustomCSS.save, onClick: saveCss},
+ {label: , tooltip: Strings.CustomCSS.openNative, onClick: popoutNative},
+ {label: Strings.Collections.settings.customcss.liveUpdate.name, type: "checkbox", onChange: toggleLiveUpdate, checked: Settings.get("settings", "customcss", "liveUpdate"), side: "right"},
+ openDetached && {label: , tooltip: Strings.CustomCSS.openDetached, onClick: popout, side: "right"}
+ ].filter(c => c)}
+ value={css}
+ />;
+});
\ No newline at end of file
diff --git a/renderer/src/ui/customcss/editor.jsx b/renderer/src/ui/customcss/editor.jsx
index d73f8a86..9cf0b9a0 100644
--- a/renderer/src/ui/customcss/editor.jsx
+++ b/renderer/src/ui/customcss/editor.jsx
@@ -2,41 +2,76 @@ import {React, DiscordModules, Settings} from "modules";
import Checkbox from "./checkbox";
+const {useState, useCallback, useEffect, forwardRef, useMemo, useImperativeHandle} = React;
const ThemeStore = DiscordModules.ThemeStore;
+
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"];
-export default class CodeEditor extends React.Component {
- static get defaultId() {return "bd-editor";}
+function makeButton(button, value) {
+ return
+ {props => {
+ return ;
+ }}
+ ;
+}
- constructor(props) {
- super(props);
+function makeCheckbox(checkbox) {
+ return ;
+}
- this.props.theme = ThemeStore?.theme === "light" ? "vs" : "vs-dark";
+function buildControl(value, control) {
+ if (control.type == "checkbox") return makeCheckbox(control);
+ return makeButton(control, value);
+}
- this.props.language = this.props.language.toLowerCase().replace(/ /g, "_");
- if (!languages.includes(this.props.language)) this.props.language = CodeEditor.defaultProps.language;
+export default forwardRef(function CodeEditor({value, language: requestedLang = "css", id = "bd-editor", controls = [], onChange: notifyParent}, ref) {
+ const language = useMemo(() => {
+ const requested = requestedLang.toLowerCase().replace(/ /g, "_");
+ if (!languages.includes(requested)) return "css";
+ return requested;
+ }, [requestedLang]);
- this.bindings = [];
- this.resize = this.resize.bind(this);
- this.onChange = this.onChange.bind(this);
- this.onThemeChange = this.onThemeChange.bind(this);
- }
+ const [theme, setTheme] = useState(() => ThemeStore?.theme === "light" ? "vs" : "vs-dark");
+ const [editor, setEditor] = useState(null);
+ const [bindings, setBindings] = useState([]);
- static get defaultProps() {
+ const onThemeChange = useCallback(() => {
+ const newTheme = ThemeStore?.theme === "light" ? "vs" : "vs-dark";
+ if (newTheme === theme) return;
+ if (window.monaco?.editor) window.monaco.editor.setTheme(newTheme);
+ setTheme(newTheme);
+ }, [theme]);
+
+ const onChange = useCallback(() => {
+ notifyParent?.(editor?.getValue());
+ }, [editor]);
+ const resize = useCallback(() => editor.layout(), [editor]);
+ const showSettings = useCallback(() => editor.keyBinding.$defaultHandler.commands.showSettingsMenu.exec(editor), [editor]);
+
+ useImperativeHandle(ref, () => {
return {
- controls: [],
- language: "css",
- id: this.defaultId
+ resize,
+ showSettings,
+ get value() {return editor.getValue();},
+ set value(newValue) {editor.setValue(newValue);}
};
- }
+ }, [editor]);
- componentDidMount() {
+ useEffect(() => {
+ setBindings([...bindings, editor?.onDidChangeModelContent(onChange)]);
+ return () => {
+ for (const binding of bindings) binding?.dispose();
+ setBindings([]);
+ };
+ }, [editor]);
+
+ useEffect(() => {
if (window.monaco?.editor) {
- this.editor = window.monaco.editor.create(document.getElementById(this.props.id), {
- value: this.props.value,
- language: this.props.language,
- theme: ThemeStore?.theme == "light" ? "vs" : "vs-dark",
+ const monacoEditor = window.monaco.editor.create(document.getElementById(id), {
+ value: value,
+ language: language,
+ theme: theme,
fontSize: Settings.get("settings", "editor", "fontSize"),
lineNumbers: Settings.get("settings", "editor", "lineNumbers"),
minimap: {enabled: Settings.get("settings", "editor", "minimap")},
@@ -49,89 +84,53 @@ export default class CodeEditor extends React.Component {
renderWhitespace: Settings.get("settings", "editor", "renderWhitespace")
});
- this.bindings.push(this.editor.onDidChangeModelContent(this.onChange));
+ setEditor(monacoEditor);
}
else {
const textarea = document.createElement("textarea");
textarea.className = "bd-fallback-editor";
- textarea.value = this.props.value;
- textarea.onchange = (e) => this.onChange(e.target.value);
- textarea.oninput = (e) => this.onChange(e.target.value);
+ textarea.value = value;
+ textarea.onchange = (e) => onChange(e.target.value);
+ textarea.oninput = (e) => onChange(e.target.value);
- this.editor = {
+ setEditor({
dispose: () => textarea.remove(),
getValue: () => textarea.value,
- setValue: (value) => textarea.value = value,
+ setValue: (val) => textarea.value = val,
layout: () => {},
- };
+ });
- document.getElementById(this.props.id).appendChild(textarea);
+ document.getElementById(id).appendChild(textarea);
}
- ThemeStore?.addChangeListener?.(this.onThemeChange);
- window.addEventListener("resize", this.resize);
- }
+ ThemeStore?.addChangeListener?.(onThemeChange);
+ window.addEventListener("resize", resize);
- componentWillUnmount() {
- window.removeEventListener("resize", this.resize);
- ThemeStore?.removeChangeListener?.(this.onThemeChange);
- for (const binding of this.bindings) binding.dispose();
- this.editor.dispose();
- }
+ return () => {
+ window.removeEventListener("resize", resize);
+ ThemeStore?.removeChangeListener?.(onThemeChange);
+ editor?.dispose();
+ };
+ }, []);
- onThemeChange() {
- const newTheme = ThemeStore?.theme === "light" ? "vs" : "vs-dark";
- if (newTheme === this.props.theme) return;
- this.props.theme = newTheme;
- if (window.monaco?.editor) window.monaco.editor.setTheme(this.props.theme);
- }
- get value() {return this.editor.getValue();}
- set value(newValue) {this.editor.setValue(newValue);}
+ if (editor && editor.layout) editor.layout();
- onChange() {
- if (this.props.onChange) this.props.onChange(this.value);
- }
+ const controlsLeft = controls.filter(c => c.side != "right").map(buildControl.bind(null, () => editor?.getValue()));
+ const controlsRight = controls.filter(c => c.side == "right").map(buildControl.bind(null, () => editor?.getValue()));
- showSettings() {return this.editor.keyBinding.$defaultHandler.commands.showSettingsMenu.exec(this.editor);}
- resize() {this.editor.layout();}
-
- buildControl(control) {
- if (control.type == "checkbox") return this.makeCheckbox(control);
- return this.makeButton(control);
- }
-
- makeCheckbox(checkbox) {
- return ;
- }
-
- makeButton(button) {
- return
- {props => {
- return ;
- }}
- ;
- }
-
- render() {
- 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));
-
- return
-
-
- {controlsLeft}
-
-
- {controlsRight}
-
+ return
+
+
+ {controlsLeft}
-
;
- }
-}
+
+
+
;
+});
\ No newline at end of file
diff --git a/renderer/src/ui/misc/addoneditor.jsx b/renderer/src/ui/misc/addoneditor.jsx
index eeca49a0..0c5f042a 100644
--- a/renderer/src/ui/misc/addoneditor.jsx
+++ b/renderer/src/ui/misc/addoneditor.jsx
@@ -4,60 +4,39 @@ import Editor from "../customcss/editor";
import Save from "../icons/save";
import Edit from "../icons/edit";
-export default class AddonEditor extends React.Component {
+const {useState, useCallback, forwardRef, useImperativeHandle, useRef} = React;
- constructor(props) {
- super(props);
- this.hasUnsavedChanges = false;
- this.onChange = this.onChange.bind(this);
- this.save = this.save.bind(this);
- this.openNative = this.openNative.bind(this);
- this.update = this.update.bind(this);
+export default forwardRef(function AddonEditor({content, language, save, openNative, id = "bd-addon-editor"}, ref) {
+ const editorRef = useRef(null);
+ const [hasUnsavedChanges, setUnsaved] = useState(false);
- 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}
- ];
- }
+ useImperativeHandle(ref, () => {
+ return {
+ resize() {editorRef.current.resize();},
+ showSettings() {editorRef.current.showSettings();},
+ get value() {return editorRef.current.getValue();},
+ set value(newValue) {editorRef.current.setValue(newValue);},
+ get hasUnsavedChanges() {return hasUnsavedChanges;}
+ };
+ }, []);
- update() {
- this.forceUpdate();
- }
+ const popoutNative = useCallback(() => openNative?.(), []);
+ const onChange = useCallback(() => setUnsaved(true), []);
+ const saveAddon = useCallback((event, newCSS) => {
+ save?.(newCSS);
+ setUnsaved(false);
+ }, []);
- updateEditor(newCSS) {
- if (!this.editor) return;
- this.editor.value = newCSS;
- }
-
- get value() {return this.editor.session.getValue();}
- set value(newValue) {
- this.editor.setValue(newValue);
- }
-
- showSettings() {return this.editor.keyBinding.$defaultHandler.commands.showSettingsMenu.exec(this.editor);}
- resize() {return this.editor.resize();}
-
- setEditorRef(editor) {
- this.editor = editor;
- if (this.props.editorRef && typeof(this.props.editorRef.current) !== "undefined") this.props.editorRef.current = editor;
- else if (this.props.editorRef) this.props.editorRef = editor;
- }
-
- render() {
- return
;
- }
-
- onChange() {
- this.hasUnsavedChanges = true;
- }
-
- save(event, content) {
- this.hasUnsavedChanges = false;
- if (this.props.save) this.props.save(content);
- }
-
- openNative() {
- if (this.props.openNative) this.props.openNative();
- }
-}
\ No newline at end of file
+ return
, tooltip: Strings.CustomCSS.save, onClick: saveAddon},
+ {label:
, tooltip: Strings.CustomCSS.openNative, onClick: popoutNative}
+ ]}
+ value={content}
+ onChange={onChange}
+ />;
+});
\ No newline at end of file
diff --git a/renderer/src/ui/settings/addonlist.jsx b/renderer/src/ui/settings/addonlist.jsx
index 88221727..53e04dbb 100644
--- a/renderer/src/ui/settings/addonlist.jsx
+++ b/renderer/src/ui/settings/addonlist.jsx
@@ -1,4 +1,3 @@
-import Logger from "common/logger";
import {React, Strings, Events, DataStore, DiscordModules} from "modules";
import Modals from "../modals";