Lint functional hooks
This commit is contained in:
parent
c2d1e4505f
commit
7b58be079d
|
@ -21,6 +21,7 @@
|
|||
"dotenv": "^16.0.3",
|
||||
"eslint": "^8.23.0",
|
||||
"eslint-plugin-react": "^7.31.6",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"mocha": "^10.0.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
|
|
|
@ -8,6 +8,7 @@ importers:
|
|||
dotenv: ^16.0.3
|
||||
eslint: ^8.23.0
|
||||
eslint-plugin-react: ^7.31.6
|
||||
eslint-plugin-react-hooks: ^4.6.0
|
||||
mocha: ^10.0.0
|
||||
webpack: ^5.74.0
|
||||
webpack-cli: ^4.10.0
|
||||
|
@ -16,6 +17,7 @@ importers:
|
|||
dotenv: 16.0.3
|
||||
eslint: 8.23.0
|
||||
eslint-plugin-react: 7.31.6_eslint@8.23.0
|
||||
eslint-plugin-react-hooks: 4.6.0_eslint@8.23.0
|
||||
mocha: 10.0.0
|
||||
webpack: 5.74.0_webpack-cli@4.10.0
|
||||
webpack-cli: 4.10.0_webpack@5.74.0
|
||||
|
@ -2343,6 +2345,15 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-react-hooks/4.6.0_eslint@8.23.0:
|
||||
resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
|
||||
dependencies:
|
||||
eslint: 8.23.0
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-react/7.31.6_eslint@8.23.0:
|
||||
resolution: {integrity: sha512-CXu4eu28sb8Sd2+cyUYsJVyDvpTlaXPG+bOzzpS9IzZKtye96AYX3ZmHQ6ayn/OAIQ/ufDJP8ElPWd63Pepn9w==}
|
||||
engines: {node: '>=4'}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
{
|
||||
"extends": ["plugin:react/recommended"],
|
||||
"extends": [
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"plugins": [
|
||||
"react"
|
||||
"react",
|
||||
"react-hooks"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
|
|
|
@ -60,7 +60,7 @@ export default function AddonErrorModal({pluginErrors, themeErrors}) {
|
|||
}, [pluginErrors, themeErrors]);
|
||||
|
||||
const [tabId, setTab] = useState(tabs[0].id);
|
||||
const switchToTab = useCallback((id) => setTab(id), [tabId]);
|
||||
const switchToTab = useCallback((id) => setTab(id), []);
|
||||
const selectedTab = tabs.find(e => e.id === tabId);
|
||||
|
||||
return <>
|
||||
|
|
|
@ -3,15 +3,15 @@ import {React} from "modules";
|
|||
const {useState, useCallback} = React;
|
||||
|
||||
|
||||
export default function Checkbox(props) {
|
||||
const [checked, setChecked] = useState(props.checked);
|
||||
export default function Checkbox({checked: initialState, text, onChange: notifyParent}) {
|
||||
const [checked, setChecked] = useState(initialState);
|
||||
const onClick = useCallback(() => {
|
||||
props?.onChange(!checked);
|
||||
notifyParent?.(!checked);
|
||||
setChecked(!checked);
|
||||
}, [checked]);
|
||||
}, [notifyParent, checked]);
|
||||
|
||||
return <div className="checkbox-item">
|
||||
<div className="checkbox-label label-JWQiNe da-label">{props.text}</div>
|
||||
<div className="checkbox-label label-JWQiNe da-label">{text}</div>
|
||||
<div className="checkbox-wrapper checkbox-3kaeSU da-checkbox checkbox-3EVISJ da-checkbox" onClick={onClick}>
|
||||
<div className="checkbox-inner checkboxInner-3yjcPe da-checkboxInner">
|
||||
<input className="checkbox checkboxElement-1qV33p da-checkboxElement" checked={checked} type="checkbox" />
|
||||
|
|
|
@ -25,27 +25,27 @@ export default forwardRef(function CssEditor({css, openNative, update, save, onC
|
|||
set value(newValue) {editorRef.current.setValue(newValue);},
|
||||
get hasUnsavedChanges() {return hasUnsavedChanges;}
|
||||
};
|
||||
}, []);
|
||||
}, [hasUnsavedChanges]);
|
||||
|
||||
useEffect(() => {
|
||||
Events.on("customcss-updated", updateEditor);
|
||||
return () => Events.off("customcss-updated", updateEditor);
|
||||
});
|
||||
}, [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), []);
|
||||
const updateCss = useCallback((event, newCSS) => update?.(newCSS), [update]);
|
||||
const popoutNative = useCallback(() => openNative?.(), [openNative]);
|
||||
const popout = useCallback((event, currentCSS) => openDetached?.(currentCSS), [openDetached]);
|
||||
|
||||
const onChange = useCallback((newCSS) => {
|
||||
notifyParent?.(newCSS);
|
||||
setUnsaved(true);
|
||||
}, []);
|
||||
}, [notifyParent]);
|
||||
|
||||
const saveCss = useCallback((event, newCSS) => {
|
||||
save?.(newCSS);
|
||||
setUnsaved(false);
|
||||
}, []);
|
||||
}, [save]);
|
||||
|
||||
|
||||
return <Editor
|
||||
|
|
|
@ -34,7 +34,7 @@ export default forwardRef(function CodeEditor({value, language: requestedLang =
|
|||
|
||||
const [theme, setTheme] = useState(() => ThemeStore?.theme === "light" ? "vs" : "vs-dark");
|
||||
const [editor, setEditor] = useState(null);
|
||||
const [bindings, setBindings] = useState([]);
|
||||
const [, setBindings] = useState([]);
|
||||
|
||||
const onThemeChange = useCallback(() => {
|
||||
const newTheme = ThemeStore?.theme === "light" ? "vs" : "vs-dark";
|
||||
|
@ -45,7 +45,7 @@ export default forwardRef(function CodeEditor({value, language: requestedLang =
|
|||
|
||||
const onChange = useCallback(() => {
|
||||
notifyParent?.(editor?.getValue());
|
||||
}, [editor]);
|
||||
}, [editor, notifyParent]);
|
||||
const resize = useCallback(() => editor.layout(), [editor]);
|
||||
const showSettings = useCallback(() => editor.keyBinding.$defaultHandler.commands.showSettingsMenu.exec(editor), [editor]);
|
||||
|
||||
|
@ -56,17 +56,20 @@ export default forwardRef(function CodeEditor({value, language: requestedLang =
|
|||
get value() {return editor.getValue();},
|
||||
set value(newValue) {editor.setValue(newValue);}
|
||||
};
|
||||
}, [editor]);
|
||||
}, [editor, resize, showSettings]);
|
||||
|
||||
useEffect(() => {
|
||||
setBindings([...bindings, editor?.onDidChangeModelContent(onChange)]);
|
||||
setBindings(bins => [...bins, editor?.onDidChangeModelContent(onChange)]);
|
||||
return () => {
|
||||
for (const binding of bindings) binding?.dispose();
|
||||
setBindings([]);
|
||||
setBindings(bins => {
|
||||
for (const binding of bins) binding?.dispose();
|
||||
return [];
|
||||
});
|
||||
};
|
||||
}, [editor]);
|
||||
}, [editor, onChange]);
|
||||
|
||||
useEffect(() => {
|
||||
let toDispose = null;
|
||||
if (window.monaco?.editor) {
|
||||
const monacoEditor = window.monaco.editor.create(document.getElementById(id), {
|
||||
value: value,
|
||||
|
@ -84,6 +87,7 @@ export default forwardRef(function CodeEditor({value, language: requestedLang =
|
|||
renderWhitespace: Settings.get("settings", "editor", "renderWhitespace")
|
||||
});
|
||||
|
||||
toDispose = monacoEditor;
|
||||
setEditor(monacoEditor);
|
||||
}
|
||||
else {
|
||||
|
@ -91,28 +95,35 @@ export default forwardRef(function CodeEditor({value, language: requestedLang =
|
|||
const textarea = document.createElement("textarea");
|
||||
textarea.className = "bd-fallback-editor";
|
||||
textarea.value = value;
|
||||
textarea.onchange = (e) => onChange(e.target.value);
|
||||
textarea.oninput = (e) => onChange(e.target.value);
|
||||
|
||||
setEditor({
|
||||
dispose: () => textarea.remove(),
|
||||
getValue: () => textarea.value,
|
||||
setValue: (val) => textarea.value = val,
|
||||
layout: () => {},
|
||||
onDidChangeModelContent: (cb) => {
|
||||
textarea.onchange = cb;
|
||||
textarea.oninput = cb;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById(id).appendChild(textarea);
|
||||
}
|
||||
|
||||
return () => {
|
||||
toDispose?.dispose?.();
|
||||
};
|
||||
}, [id, language, theme, value]);
|
||||
|
||||
useEffect(() => {
|
||||
ThemeStore?.addChangeListener?.(onThemeChange);
|
||||
window.addEventListener("resize", resize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", resize);
|
||||
ThemeStore?.removeChangeListener?.(onThemeChange);
|
||||
editor?.dispose();
|
||||
};
|
||||
}, []);
|
||||
}, [onThemeChange, resize]);
|
||||
|
||||
|
||||
if (editor && editor.layout) editor.layout();
|
||||
|
|
|
@ -12,15 +12,10 @@ function minY() {
|
|||
}
|
||||
|
||||
export default function FloatingWindowContainer() {
|
||||
useEffect(() => {
|
||||
Events.on("open-window", open);
|
||||
return () => Events.off("open-window", open);
|
||||
}, []);
|
||||
|
||||
const [windows, setWindows] = useState([]);
|
||||
const open = useCallback(window => {
|
||||
setWindows([...windows, window]);
|
||||
}, [windows]);
|
||||
setWindows(wins => [...wins, window]);
|
||||
}, []);
|
||||
const close = useCallback(id => {
|
||||
setWindows(windows.filter(w => {
|
||||
if (w.id === id && w.onClose) w.onClose();
|
||||
|
@ -28,6 +23,11 @@ export default function FloatingWindowContainer() {
|
|||
}));
|
||||
}, [windows]);
|
||||
|
||||
useEffect(() => {
|
||||
Events.on("open-window", open);
|
||||
return () => Events.off("open-window", open);
|
||||
}, [open]);
|
||||
|
||||
return windows.map(window =>
|
||||
<FloatingWindow {...window} close={() => close(window.id)} minY={minY()} key={window.id}>
|
||||
{window.children}
|
||||
|
|
|
@ -5,7 +5,7 @@ import CloseButton from "../icons/close";
|
|||
import MaximizeIcon from "../icons/fullscreen";
|
||||
import Modals from "../modals";
|
||||
|
||||
const {useState, useCallback, useEffect, useRef, useMemo} = React;
|
||||
const {useState, useCallback, useEffect, useRef} = React;
|
||||
|
||||
|
||||
function confirmClose(confirmationText) {
|
||||
|
@ -19,18 +19,13 @@ function confirmClose(confirmationText) {
|
|||
});
|
||||
}
|
||||
|
||||
export default function FloatingWindow(props) {
|
||||
export default function FloatingWindow({id, title, resizable, children, className, center, top: initialTop, left: initialLeft, width: initialWidth, height: initialHeight, minX = 0, minY = 0, maxX = Screen.width, maxY = Screen.height, onResize, close: doClose, confirmClose: doConfirmClose, confirmationText}) {
|
||||
const [modalOpen, setOpen] = useState(false);
|
||||
const [isDragging, setDragging] = useState(false);
|
||||
const [position, setPosition] = useState({x: props.center ? (Screen.width / 2) - (props.width / 2) : props.left, y: props.center ? (Screen.height / 2) - (props.height / 2) : props.top});
|
||||
const [position, setPosition] = useState({x: center ? (Screen.width / 2) - (initialWidth / 2) : initialLeft, y: center ? (Screen.height / 2) - (initialHeight / 2) : initialTop});
|
||||
const [offset, setOffset] = useState({x: 0, y: 0});
|
||||
const [size, setSize] = useState({width: 0, height: 0});
|
||||
|
||||
const minX = useMemo(() => props.minX || 0);
|
||||
const maxX = useMemo(() => props.maxX || Screen.width);
|
||||
const minY = useMemo(() => props.minY || 0);
|
||||
const maxY = useMemo(() => props.maxY || Screen.height);
|
||||
|
||||
const titlebar = useRef(null);
|
||||
const window = useRef(null);
|
||||
|
||||
|
@ -51,7 +46,7 @@ export default function FloatingWindow(props) {
|
|||
if (newLeft + size.width >= maxX) newLeft = maxX - size.width;
|
||||
|
||||
setPosition({x: newLeft, y: newTop});
|
||||
}, [window, offset, size, isDragging]);
|
||||
}, [offset, size, isDragging, minX, minY, maxX, maxY]);
|
||||
|
||||
|
||||
const onDragStart = useCallback((e) => {
|
||||
|
@ -66,7 +61,7 @@ export default function FloatingWindow(props) {
|
|||
const width = window.current.offsetWidth;
|
||||
const height = window.current.offsetHeight;
|
||||
if (width != size.width || height != size.height) {
|
||||
if (props.onResize) props.onResize();
|
||||
if (onResize) onResize();
|
||||
const left = parseInt(window.current.style.left);
|
||||
const top = parseInt(window.current.style.top);
|
||||
if (left + width >= maxX) window.current.style.width = (maxX - left) + "px";
|
||||
|
@ -74,20 +69,22 @@ export default function FloatingWindow(props) {
|
|||
}
|
||||
|
||||
setSize({width, height});
|
||||
}, [window, size, onDrag]);
|
||||
}, [window, size, maxX, maxY, onResize]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
window.current.addEventListener("mousedown", onResizeStart, false);
|
||||
titlebar.current.addEventListener("mousedown", onDragStart, false);
|
||||
const winRef = window.current;
|
||||
const titleRef = titlebar.current;
|
||||
winRef.addEventListener("mousedown", onResizeStart, false);
|
||||
titleRef.addEventListener("mousedown", onDragStart, false);
|
||||
document.addEventListener("mouseup", onDragStop, false);
|
||||
document.addEventListener("mousemove", onDrag, true);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mouseup", onDragStop, false);
|
||||
document.removeEventListener("mousemove", onDrag, true);
|
||||
window?.current?.removeEventListener("mousedown", onResizeStart, false);
|
||||
titlebar?.current?.removeEventListener("mousedown", onDragStart, false);
|
||||
winRef.removeEventListener("mousedown", onResizeStart, false);
|
||||
titleRef.removeEventListener("mousedown", onDragStart, false);
|
||||
};
|
||||
}, [titlebar, window, onDragStart, onDragStop, onDrag, onResizeStart]);
|
||||
|
||||
|
@ -95,7 +92,7 @@ export default function FloatingWindow(props) {
|
|||
const maximize = useCallback(() => {
|
||||
window.current.style.width = "100%";
|
||||
window.current.style.height = "100%";
|
||||
if (props.onResize) props.onResize();
|
||||
if (onResize) onResize();
|
||||
|
||||
const width = window.current.offsetWidth;
|
||||
const height = window.current.offsetHeight;
|
||||
|
@ -123,27 +120,27 @@ export default function FloatingWindow(props) {
|
|||
window.current.style.left = minX + "px";
|
||||
window.current.style.height = (width - difference) + "px";
|
||||
}
|
||||
}, [window, minX, minY, maxX, maxY]);
|
||||
}, [window, minX, minY, maxX, maxY, onResize]);
|
||||
|
||||
|
||||
const close = useCallback(async () => {
|
||||
let shouldClose = true;
|
||||
const didConfirmClose = typeof(props.confirmClose) == "function" ? props.confirmClose() : props.confirmClose;
|
||||
const didConfirmClose = typeof(doConfirmClose) == "function" ? doConfirmClose() : doConfirmClose;
|
||||
if (didConfirmClose) {
|
||||
setOpen(true);
|
||||
shouldClose = await confirmClose(props.confirmationText);
|
||||
shouldClose = await confirmClose(confirmationText);
|
||||
setOpen(false);
|
||||
}
|
||||
if (props.close && shouldClose) props.close();
|
||||
}, []);
|
||||
if (doClose && shouldClose) doClose();
|
||||
}, [confirmationText, doClose, doConfirmClose]);
|
||||
|
||||
|
||||
|
||||
const className = `floating-window${` ${props.className}` || ""}${props.resizable ? " resizable" : ""}${modalOpen ? " modal-open" : ""}`;
|
||||
const styles = {height: props.height, width: props.width, left: position.x || 0, top: position.y || 0};
|
||||
return <div id={props.id} className={className} ref={window} style={styles}>
|
||||
const finalClassname = `floating-window${` ${className}` || ""}${resizable ? " resizable" : ""}${modalOpen ? " modal-open" : ""}`;
|
||||
const styles = {height: initialHeight, width: initialWidth, left: position.x || 0, top: position.y || 0};
|
||||
return <div id={id} className={finalClassname} ref={window} style={styles}>
|
||||
<div className="floating-window-titlebar" ref={titlebar}>
|
||||
<span className="title">{props.title}</span>
|
||||
<span className="title">{title}</span>
|
||||
<div className="floating-window-buttons">
|
||||
<div className="button maximize-button" onClick={maximize}>
|
||||
<MaximizeIcon size="18px" />
|
||||
|
@ -154,7 +151,7 @@ export default function FloatingWindow(props) {
|
|||
</div>
|
||||
</div>
|
||||
<div className="floating-window-content">
|
||||
{props.children}
|
||||
{children}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
|
@ -19,14 +19,14 @@ export default forwardRef(function AddonEditor({content, language, save, openNat
|
|||
set value(newValue) {editorRef.current.setValue(newValue);},
|
||||
get hasUnsavedChanges() {return hasUnsavedChanges;}
|
||||
};
|
||||
}, []);
|
||||
}, [hasUnsavedChanges]);
|
||||
|
||||
const popoutNative = useCallback(() => openNative?.(), []);
|
||||
const popoutNative = useCallback(() => openNative?.(), [openNative]);
|
||||
const onChange = useCallback(() => setUnsaved(true), []);
|
||||
const saveAddon = useCallback((event, newCSS) => {
|
||||
save?.(newCSS);
|
||||
setUnsaved(false);
|
||||
}, []);
|
||||
}, [save]);
|
||||
|
||||
return <Editor
|
||||
ref={editorRef}
|
||||
|
|
|
@ -27,9 +27,9 @@ export default function ServerCard({server, joined, join, navigateTo, defaultAva
|
|||
setJoined("joining");
|
||||
const didJoin = await join(server.identifier, server.nativeJoin);
|
||||
setJoined(didJoin);
|
||||
}, [hasJoined]);
|
||||
}, [hasJoined, join, navigateTo, server.identifier, server.nativeJoin]);
|
||||
|
||||
const defaultIcon = useMemo(() => defaultAvatar(), []);
|
||||
const defaultIcon = useMemo(() => defaultAvatar(), [defaultAvatar]);
|
||||
const currentIcon = !server.iconUrl || isError ? defaultIcon : server.iconUrl;
|
||||
|
||||
const addedDate = new Date(server.insertDate * 1000); // Convert from unix timestamp
|
||||
|
|
|
@ -85,7 +85,7 @@ export default function AddonCard({addon, type, disabled, enabled, onChange: par
|
|||
const onChange = useCallback(() => {
|
||||
setEnabled(!isEnabled);
|
||||
if (parentChange) parentChange(addon.id);
|
||||
}, []);
|
||||
}, [addon.id, parentChange, isEnabled]);
|
||||
|
||||
const showSettings = useCallback(() => {
|
||||
if (!hasSettings || !enabled) return;
|
||||
|
@ -97,7 +97,7 @@ export default function AddonCard({addon, type, disabled, enabled, onChange: par
|
|||
Toasts.show(Strings.Addons.settingsError.format({name}), {type: "error"});
|
||||
Logger.stacktrace("Addon Settings", "Unable to get settings panel for " + name + ".", err);
|
||||
}
|
||||
}, [hasSettings, enabled]);
|
||||
}, [hasSettings, enabled, addon.name, getSettingsPanel]);
|
||||
|
||||
const messageAuthor = useCallback(() => {
|
||||
if (!addon.authorId) return;
|
||||
|
@ -127,7 +127,7 @@ export default function AddonCard({addon, type, disabled, enabled, onChange: par
|
|||
{authorArray}
|
||||
</div>
|
||||
];
|
||||
}, []);
|
||||
}, [addon.name, addon.version, addon.authorLink, addon.authorId, addon.author, messageAuthor]);
|
||||
|
||||
const footer = useMemo(() => {
|
||||
const links = Object.keys(LinkIcons);
|
||||
|
@ -140,7 +140,7 @@ export default function AddonCard({addon, type, disabled, enabled, onChange: par
|
|||
{deleteAddon && makeButton(Strings.Addons.deleteAddon, <DeleteIcon size={"20px"} />, deleteAddon, {isControl: true, danger: true})}
|
||||
</div>
|
||||
</div>;
|
||||
}, [hasSettings, editAddon, deleteAddon]);
|
||||
}, [hasSettings, editAddon, deleteAddon, addon, enabled, showSettings]);
|
||||
|
||||
return <div id={`${addon.id}-card`} className={"bd-addon-card" + (disabled ? " bd-addon-card-disabled" : "")}>
|
||||
<div className="bd-addon-header">
|
||||
|
|
|
@ -90,36 +90,36 @@ export default function AddonList({prefix, type, title, folder, addonList, addon
|
|||
Events.off(`${prefix}-loaded`, forceUpdate);
|
||||
Events.off(`${prefix}-unloaded`, forceUpdate);
|
||||
};
|
||||
}, []);
|
||||
}, [prefix]);
|
||||
|
||||
const changeView = useCallback((value) => {
|
||||
saveState(type, "view", value);
|
||||
setView(value);
|
||||
}, []);
|
||||
}, [type]);
|
||||
|
||||
const listView = useCallback(() => changeView("list"), []);
|
||||
const gridView = useCallback(() => changeView("grid"), []);
|
||||
const listView = useCallback(() => changeView("list"), [changeView]);
|
||||
const gridView = useCallback(() => changeView("grid"), [changeView]);
|
||||
|
||||
const changeDirection = useCallback((value) => {
|
||||
saveState(type, "ascending", value);
|
||||
setAscending(value);
|
||||
}, []);
|
||||
}, [type]);
|
||||
|
||||
const changeSort = useCallback((value) => {
|
||||
saveState(type, "sort", value);
|
||||
setSort(value);
|
||||
}, []);
|
||||
}, [type]);
|
||||
|
||||
const search = useCallback((e) => setQuery(e.target.value.toLocaleLowerCase()), []);
|
||||
const triggerEdit = useCallback((id) => editAddon?.(id), []);
|
||||
const triggerEdit = useCallback((id) => editAddon?.(id), [editAddon]);
|
||||
const triggerDelete = useCallback(async (id) => {
|
||||
const addon = addonList.find(a => a.id == id);
|
||||
const shouldDelete = await confirmDelete(addon);
|
||||
if (!shouldDelete) return;
|
||||
if (deleteAddon) deleteAddon(addon);
|
||||
}, []);
|
||||
}, [addonList, deleteAddon]);
|
||||
|
||||
const button = folder ? {title: Strings.Addons.openFolder.format({type: title}), onClick: openFolder} : null;
|
||||
const button = folder ? {title: Strings.Addons.openFolder.format({type: title}), onClick: openFolder.bind(null, folder)} : null;
|
||||
const renderedCards = useMemo(() => {
|
||||
let sorted = addonList.sort((a, b) => {
|
||||
const sortByEnabled = sort === "isEnabled";
|
||||
|
@ -150,7 +150,7 @@ export default function AddonList({prefix, type, title, folder, addonList, addon
|
|||
const getSettings = hasSettings && addon.instance.getSettingsPanel.bind(addon.instance);
|
||||
return <ErrorBoundary><AddonCard disabled={addon.partial} type={type} editAddon={() => triggerEdit(addon.id)} deleteAddon={() => triggerDelete(addon.id)} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} hasSettings={hasSettings} getSettingsPanel={getSettings} /></ErrorBoundary>;
|
||||
});
|
||||
}, [addonList, addonState, sort, ascending, query]);
|
||||
}, [addonList, addonState, onChange, reload, triggerDelete, triggerEdit, type, sort, ascending, query]);
|
||||
|
||||
const hasAddonsInstalled = addonList.length !== 0;
|
||||
const isSearching = !!query;
|
||||
|
|
|
@ -60,7 +60,7 @@ export default function Color({value: initialValue, onChange, colors = defaultCo
|
|||
const change = useCallback((e) => {
|
||||
onChange?.(resolveColor(e.target.value));
|
||||
setValue(e.target.value);
|
||||
}, []);
|
||||
}, [onChange]);
|
||||
|
||||
const intValue = resolveColor(value, false);
|
||||
return <div className="bd-color-picker-container">
|
||||
|
|
|
@ -9,7 +9,7 @@ export default function Select({value: initialValue, options, style, onChange})
|
|||
const change = useCallback((val) => {
|
||||
onChange?.(val);
|
||||
setValue(val);
|
||||
}, []);
|
||||
}, [onChange]);
|
||||
|
||||
|
||||
const hideMenu = useCallback(() => {
|
||||
|
@ -26,7 +26,7 @@ export default function Select({value: initialValue, options, style, onChange})
|
|||
setOpen(next);
|
||||
if (!next) return;
|
||||
document.addEventListener("click", hideMenu);
|
||||
}, [open]);
|
||||
}, [hideMenu, open]);
|
||||
|
||||
|
||||
// ?? options[0] provides a double failsafe
|
||||
|
|
|
@ -26,19 +26,19 @@ export default function Keybind({value: initialValue, onChange, max = 2, clearab
|
|||
if (onChange) onChange(state.accum);
|
||||
setState({value: state.accum.slice(0), isRecording: false, accum: []});
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
const onClick = useCallback((e) => {
|
||||
if (e.target?.className?.includes?.("bd-keybind-clear") || e.target?.closest(".bd-button")?.className?.includes("bd-keybind-clear")) return clearKeybind(e);
|
||||
setState({...state, isRecording: !state.isRecording});
|
||||
}, [state]);
|
||||
}, [state, max, onChange]);
|
||||
|
||||
const clearKeybind = useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if (onChange) onChange([]);
|
||||
setState({...state, value: [], accum: []});
|
||||
}, []);
|
||||
}, [onChange, state]);
|
||||
|
||||
const onClick = useCallback((e) => {
|
||||
if (e.target?.className?.includes?.("bd-keybind-clear") || e.target?.closest(".bd-button")?.className?.includes("bd-keybind-clear")) return clearKeybind(e);
|
||||
setState({...state, isRecording: !state.isRecording});
|
||||
}, [state, clearKeybind]);
|
||||
|
||||
|
||||
const displayValue = state.isRecording ? "Recording..." : !state.value.length ? "N/A" : state.value.join(" + ");
|
||||
|
|
|
@ -8,7 +8,7 @@ export default function Number({value: initialValue, min, max, step, onChange})
|
|||
const change = useCallback((e) => {
|
||||
onChange?.(e.target.value);
|
||||
setValue(e.target.value);
|
||||
}, []);
|
||||
}, [onChange]);
|
||||
|
||||
return <input onChange={change} type="number" className="bd-number-input" min={min} max={max} step={step} value={value} />;
|
||||
}
|
|
@ -12,7 +12,7 @@ export default function Radio({name, value, options, onChange}) {
|
|||
const newValue = options[newIndex].value;
|
||||
onChange?.(newValue);
|
||||
setIndex(newIndex);
|
||||
}, [index, options]);
|
||||
}, [options, onChange]);
|
||||
|
||||
function renderOption(opt, i) {
|
||||
const isSelected = index === i;
|
||||
|
|
|
@ -9,7 +9,7 @@ export default function Search({onChange, className, onKeyDown, placeholder}) {
|
|||
const change = useCallback((e) => {
|
||||
onChange?.(e);
|
||||
setValue(e.target.value);
|
||||
}, []);
|
||||
}, [onChange]);
|
||||
|
||||
|
||||
return <div className={"bd-search-wrapper" + (className ? ` ${className}` : "")}>
|
||||
|
|
|
@ -8,7 +8,7 @@ export default function Slider({value: initialValue, min, max, step, onChange})
|
|||
const change = useCallback((e) => {
|
||||
onChange?.(e.target.value);
|
||||
setValue(e.target.value);
|
||||
}, []);
|
||||
}, [onChange]);
|
||||
|
||||
return <div className="bd-slider-wrap">
|
||||
<div className="bd-slider-label">{value}</div><input onChange={change} type="range" className="bd-slider-input" min={min} max={max} step={step} value={value} style={{backgroundSize: (value - min) * 100 / (max - min) + "% 100%"}} />
|
||||
|
|
|
@ -8,7 +8,7 @@ export default function Switch({id, checked: initialValue, disabled, onChange})
|
|||
const change = useCallback(() => {
|
||||
onChange?.(!checked);
|
||||
setChecked(!checked);
|
||||
}, [checked]);
|
||||
}, [checked, onChange]);
|
||||
|
||||
const enabledClass = disabled ? " bd-switch-disabled" : "";
|
||||
const checkedClass = checked ? " bd-switch-checked" : "";
|
||||
|
|
|
@ -8,7 +8,7 @@ export default function Textbox({value: initialValue, maxLength, placeholder, on
|
|||
const change = useCallback((e) => {
|
||||
onChange?.(e.target.value);
|
||||
setValue(e.target.value);
|
||||
}, []);
|
||||
}, [onChange]);
|
||||
|
||||
return <input onChange={change} onKeyDown={onKeyDown} type="text" className="bd-text-input" placeholder={placeholder} maxLength={maxLength} value={value} />;
|
||||
}
|
|
@ -22,7 +22,7 @@ export default function Drawer({name, collapsible, shown = true, showDivider, ch
|
|||
drawer.classList.remove("animating");
|
||||
}, timeout);
|
||||
|
||||
}, [collapsed]);
|
||||
}, [collapsed, onDrawerToggle]);
|
||||
|
||||
|
||||
const onClick = useCallback((event) => {
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function Group({onChange, id, name, button, shown, onDrawerToggle
|
|||
const change = useCallback((settingId, value) => {
|
||||
if (id) onChange?.(id, settingId, value);
|
||||
else onChange?.(settingId, value);
|
||||
}, [id]);
|
||||
}, [id, onChange]);
|
||||
|
||||
return <Drawer collapsible={collapsible} name={name} button={button} shown={shown} onDrawerToggle={onDrawerToggle} showDivider={showDivider}>
|
||||
{settings.filter(s => !s.hidden).map((setting) => {
|
||||
|
|
|
@ -11,7 +11,7 @@ export default function SettingsTitle({isGroup, className, button, onClick, text
|
|||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
button?.onClick?.(event);
|
||||
}, []);
|
||||
}, [button]);
|
||||
|
||||
|
||||
const baseClass = isGroup ? groupClass : basicClass;
|
||||
|
|
|
@ -9,50 +9,50 @@ import Checkmark from "./icons/check";
|
|||
|
||||
const {useState, useCallback, useEffect} = React;
|
||||
|
||||
function CoreUpdaterPanel(props) {
|
||||
function CoreUpdaterPanel({hasUpdate, remoteVersion, update}) {
|
||||
return <Drawer name="BetterDiscord" collapsible={true}>
|
||||
<SettingItem name={`Core v${Config.version}`} note={props.hasUpdate ? Strings.Updater.versionAvailable.format({version: props.remoteVersion}) : Strings.Updater.noUpdatesAvailable} inline={true} id={"core-updater"}>
|
||||
{!props.hasUpdate && <div className="bd-filled-checkmark"><Checkmark /></div>}
|
||||
{props.hasUpdate && <button className="bd-button" onClick={props.update}>{Strings.Updater.updateButton}</button>}
|
||||
<SettingItem name={`Core v${Config.version}`} note={hasUpdate ? Strings.Updater.versionAvailable.format({version: remoteVersion}) : Strings.Updater.noUpdatesAvailable} inline={true} id={"core-updater"}>
|
||||
{!hasUpdate && <div className="bd-filled-checkmark"><Checkmark /></div>}
|
||||
{hasUpdate && <button className="bd-button" onClick={update}>{Strings.Updater.updateButton}</button>}
|
||||
</SettingItem>
|
||||
</Drawer>;
|
||||
}
|
||||
|
||||
function NoUpdates(props) {
|
||||
function NoUpdates({type}) {
|
||||
return <div className="bd-empty-updates">
|
||||
<Checkmark size="48px" />
|
||||
{Strings.Updater.upToDateBlankslate.format({type: props.type})}
|
||||
{Strings.Updater.upToDateBlankslate.format({type: type})}
|
||||
</div>;
|
||||
}
|
||||
|
||||
function AddonUpdaterPanel(props) {
|
||||
const filenames = props.pending;
|
||||
return <Drawer name={Strings.Panels[props.type]} collapsible={true} button={filenames.length ? {title: Strings.Updater.updateAll, onClick: () => props.updateAll(props.type)} : null}>
|
||||
{!filenames.length && <NoUpdates type={props.type} />}
|
||||
function AddonUpdaterPanel({pending, type, updater, update, updateAll}) {
|
||||
const filenames = pending;
|
||||
return <Drawer name={Strings.Panels[type]} collapsible={true} button={filenames.length ? {title: Strings.Updater.updateAll, onClick: () => updateAll(type)} : null}>
|
||||
{!filenames.length && <NoUpdates type={type} />}
|
||||
{filenames.map(f => {
|
||||
const info = props.updater.cache[f];
|
||||
const addon = props.updater.manager.addonList.find(a => a.filename === f);
|
||||
const info = updater.cache[f];
|
||||
const addon = updater.manager.addonList.find(a => a.filename === f);
|
||||
return <SettingItem name={`${addon.name} v${addon.version}`} note={Strings.Updater.versionAvailable.format({version: info.version})} inline={true} id={addon.name}>
|
||||
<button className="bd-button" onClick={() => props.update(props.type, f)}>{Strings.Updater.updateButton}</button>
|
||||
<button className="bd-button" onClick={() => update(type, f)}>{Strings.Updater.updateButton}</button>
|
||||
</SettingItem>;
|
||||
})}
|
||||
</Drawer>;
|
||||
}
|
||||
|
||||
export default function UpdaterPanel(props) {
|
||||
const [hasCoreUpdate, setCoreUpdate] = useState(props.coreUpdater.hasUpdate);
|
||||
const [updates, setUpdates] = useState({plugins: props.pluginUpdater.pending.slice(0), themes: props.themeUpdater.pending.slice(0)});
|
||||
export default function UpdaterPanel({coreUpdater, pluginUpdater, themeUpdater}) {
|
||||
const [hasCoreUpdate, setCoreUpdate] = useState(coreUpdater.hasUpdate);
|
||||
const [updates, setUpdates] = useState({plugins: pluginUpdater.pending.slice(0), themes: themeUpdater.pending.slice(0)});
|
||||
|
||||
const checkAddons = useCallback(async (type) => {
|
||||
const updater = type === "plugins" ? props.pluginUpdater : props.themeUpdater;
|
||||
const updater = type === "plugins" ? pluginUpdater : themeUpdater;
|
||||
await updater.checkAll(false);
|
||||
setUpdates({...updates, [type]: updater.pending.slice(0)});
|
||||
}, []);
|
||||
}, [updates, pluginUpdater, themeUpdater]);
|
||||
|
||||
const update = useCallback(() => {
|
||||
checkAddons("plugins");
|
||||
checkAddons("themes");
|
||||
}, []);
|
||||
}, [checkAddons]);
|
||||
|
||||
useEffect(() => {
|
||||
Events.on(`plugin-loaded`, update);
|
||||
|
@ -65,12 +65,12 @@ export default function UpdaterPanel(props) {
|
|||
Events.off(`theme-loaded`, update);
|
||||
Events.off(`theme-unloaded`, update);
|
||||
};
|
||||
}, []);
|
||||
}, [update]);
|
||||
|
||||
const checkCoreUpdate = useCallback(async () => {
|
||||
await props.coreUpdater.checkForUpdate(false);
|
||||
setCoreUpdate(props.coreUpdater.hasUpdate);
|
||||
}, []);
|
||||
await coreUpdater.checkForUpdate(false);
|
||||
setCoreUpdate(coreUpdater.hasUpdate);
|
||||
}, [coreUpdater]);
|
||||
|
||||
const checkForUpdates = useCallback(async () => {
|
||||
Toasts.info(Strings.Updater.checking);
|
||||
|
@ -78,33 +78,33 @@ export default function UpdaterPanel(props) {
|
|||
await checkAddons("plugins");
|
||||
await checkAddons("themes");
|
||||
Toasts.info(Strings.Updater.finishedChecking);
|
||||
});
|
||||
}, [checkAddons, checkCoreUpdate]);
|
||||
|
||||
const updateCore = useCallback(async () => {
|
||||
await props.coreUpdater.update();
|
||||
await coreUpdater.update();
|
||||
setCoreUpdate(false);
|
||||
}, []);
|
||||
}, [coreUpdater]);
|
||||
|
||||
const updateAddon = useCallback(async (type, filename) => {
|
||||
const updater = type === "plugins" ? props.pluginUpdater : props.themeUpdater;
|
||||
const updater = type === "plugins" ? pluginUpdater : themeUpdater;
|
||||
await updater.updateAddon(filename);
|
||||
setUpdates(prev => {
|
||||
prev[type].splice(prev[type].indexOf(filename), 1);
|
||||
return prev;
|
||||
});
|
||||
}, []);
|
||||
}, [pluginUpdater, themeUpdater]);
|
||||
|
||||
const updateAllAddons = useCallback(async (type) => {
|
||||
const toUpdate = updates[type].slice(0);
|
||||
for (const filename of toUpdate) {
|
||||
await updateAddon(type, filename);
|
||||
}
|
||||
}, []);
|
||||
}, [updateAddon, updates]);
|
||||
|
||||
return [
|
||||
<SettingsTitle text={Strings.Panels.updates} button={{title: Strings.Updater.checkForUpdates, onClick: checkForUpdates}} />,
|
||||
<CoreUpdaterPanel remoteVersion={props.coreUpdater.remoteVersion} hasUpdate={hasCoreUpdate} update={updateCore} />,
|
||||
<AddonUpdaterPanel type="plugins" pending={updates.plugins} update={updateAddon} updateAll={updateAllAddons} updater={props.pluginUpdater} />,
|
||||
<AddonUpdaterPanel type="themes" pending={updates.themes} update={updateAddon} updateAll={updateAllAddons} updater={props.themeUpdater} />,
|
||||
<CoreUpdaterPanel remoteVersion={coreUpdater.remoteVersion} hasUpdate={hasCoreUpdate} update={updateCore} />,
|
||||
<AddonUpdaterPanel type="plugins" pending={updates.plugins} update={updateAddon} updateAll={updateAllAddons} updater={pluginUpdater} />,
|
||||
<AddonUpdaterPanel type="themes" pending={updates.themes} update={updateAddon} updateAll={updateAllAddons} updater={themeUpdater} />,
|
||||
];
|
||||
}
|
Loading…
Reference in New Issue