diff --git a/package.json b/package.json index 18eabfca..ffcffd48 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58419660..8338201c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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'} diff --git a/renderer/.eslintrc b/renderer/.eslintrc index 9c8e7075..de1cf8bf 100644 --- a/renderer/.eslintrc +++ b/renderer/.eslintrc @@ -1,7 +1,11 @@ { - "extends": ["plugin:react/recommended"], + "extends": [ + "plugin:react/recommended", + "plugin:react-hooks/recommended" + ], "plugins": [ - "react" + "react", + "react-hooks" ], "settings": { "react": { diff --git a/renderer/src/ui/addonerrormodal.jsx b/renderer/src/ui/addonerrormodal.jsx index fdc8c120..5ac9bfaa 100644 --- a/renderer/src/ui/addonerrormodal.jsx +++ b/renderer/src/ui/addonerrormodal.jsx @@ -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 <> diff --git a/renderer/src/ui/customcss/checkbox.jsx b/renderer/src/ui/customcss/checkbox.jsx index f483e8cc..390f00f4 100644 --- a/renderer/src/ui/customcss/checkbox.jsx +++ b/renderer/src/ui/customcss/checkbox.jsx @@ -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
-
{props.text}
+
{text}
diff --git a/renderer/src/ui/customcss/csseditor.jsx b/renderer/src/ui/customcss/csseditor.jsx index d9126b4b..3c7bd776 100644 --- a/renderer/src/ui/customcss/csseditor.jsx +++ b/renderer/src/ui/customcss/csseditor.jsx @@ -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 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(); diff --git a/renderer/src/ui/floating/container.jsx b/renderer/src/ui/floating/container.jsx index 1b255da9..50008ad3 100644 --- a/renderer/src/ui/floating/container.jsx +++ b/renderer/src/ui/floating/container.jsx @@ -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 => close(window.id)} minY={minY()} key={window.id}> {window.children} diff --git a/renderer/src/ui/floating/window.jsx b/renderer/src/ui/floating/window.jsx index 1b38ca0d..220b9844 100644 --- a/renderer/src/ui/floating/window.jsx +++ b/renderer/src/ui/floating/window.jsx @@ -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
+ 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
- {props.title} + {title}
@@ -154,7 +151,7 @@ export default function FloatingWindow(props) {
- {props.children} + {children}
; } \ No newline at end of file diff --git a/renderer/src/ui/misc/addoneditor.jsx b/renderer/src/ui/misc/addoneditor.jsx index 0c5f042a..43a1048a 100644 --- a/renderer/src/ui/misc/addoneditor.jsx +++ b/renderer/src/ui/misc/addoneditor.jsx @@ -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 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 diff --git a/renderer/src/ui/settings/addoncard.jsx b/renderer/src/ui/settings/addoncard.jsx index 990b36dd..87549226 100644 --- a/renderer/src/ui/settings/addoncard.jsx +++ b/renderer/src/ui/settings/addoncard.jsx @@ -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}
]; - }, []); + }, [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, , deleteAddon, {isControl: true, danger: true})}
; - }, [hasSettings, editAddon, deleteAddon]); + }, [hasSettings, editAddon, deleteAddon, addon, enabled, showSettings]); return
diff --git a/renderer/src/ui/settings/addonlist.jsx b/renderer/src/ui/settings/addonlist.jsx index 4d18df9e..9c6282ec 100644 --- a/renderer/src/ui/settings/addonlist.jsx +++ b/renderer/src/ui/settings/addonlist.jsx @@ -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 triggerEdit(addon.id)} deleteAddon={() => triggerDelete(addon.id)} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} hasSettings={hasSettings} getSettingsPanel={getSettings} />; }); - }, [addonList, addonState, sort, ascending, query]); + }, [addonList, addonState, onChange, reload, triggerDelete, triggerEdit, type, sort, ascending, query]); const hasAddonsInstalled = addonList.length !== 0; const isSearching = !!query; diff --git a/renderer/src/ui/settings/components/color.jsx b/renderer/src/ui/settings/components/color.jsx index 31dcd30b..86040ca5 100644 --- a/renderer/src/ui/settings/components/color.jsx +++ b/renderer/src/ui/settings/components/color.jsx @@ -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
diff --git a/renderer/src/ui/settings/components/dropdown.jsx b/renderer/src/ui/settings/components/dropdown.jsx index c6ed7c99..d4d52946 100644 --- a/renderer/src/ui/settings/components/dropdown.jsx +++ b/renderer/src/ui/settings/components/dropdown.jsx @@ -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 diff --git a/renderer/src/ui/settings/components/keybind.jsx b/renderer/src/ui/settings/components/keybind.jsx index 20dd638b..c5191eee 100644 --- a/renderer/src/ui/settings/components/keybind.jsx +++ b/renderer/src/ui/settings/components/keybind.jsx @@ -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(" + "); diff --git a/renderer/src/ui/settings/components/number.jsx b/renderer/src/ui/settings/components/number.jsx index c634d0db..f6e31db0 100644 --- a/renderer/src/ui/settings/components/number.jsx +++ b/renderer/src/ui/settings/components/number.jsx @@ -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 ; } \ No newline at end of file diff --git a/renderer/src/ui/settings/components/radio.jsx b/renderer/src/ui/settings/components/radio.jsx index 99d30d60..02d5da89 100644 --- a/renderer/src/ui/settings/components/radio.jsx +++ b/renderer/src/ui/settings/components/radio.jsx @@ -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; diff --git a/renderer/src/ui/settings/components/search.jsx b/renderer/src/ui/settings/components/search.jsx index 888a6106..77ac05d0 100644 --- a/renderer/src/ui/settings/components/search.jsx +++ b/renderer/src/ui/settings/components/search.jsx @@ -9,7 +9,7 @@ export default function Search({onChange, className, onKeyDown, placeholder}) { const change = useCallback((e) => { onChange?.(e); setValue(e.target.value); - }, []); + }, [onChange]); return
diff --git a/renderer/src/ui/settings/components/slider.jsx b/renderer/src/ui/settings/components/slider.jsx index eeef48f7..75f833ac 100644 --- a/renderer/src/ui/settings/components/slider.jsx +++ b/renderer/src/ui/settings/components/slider.jsx @@ -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
{value}
diff --git a/renderer/src/ui/settings/components/switch.jsx b/renderer/src/ui/settings/components/switch.jsx index 99fe9e37..a0fdf278 100644 --- a/renderer/src/ui/settings/components/switch.jsx +++ b/renderer/src/ui/settings/components/switch.jsx @@ -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" : ""; diff --git a/renderer/src/ui/settings/components/textbox.jsx b/renderer/src/ui/settings/components/textbox.jsx index 9835ad15..3997ea6e 100644 --- a/renderer/src/ui/settings/components/textbox.jsx +++ b/renderer/src/ui/settings/components/textbox.jsx @@ -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 ; } \ No newline at end of file diff --git a/renderer/src/ui/settings/drawer.jsx b/renderer/src/ui/settings/drawer.jsx index 692b0114..97425c5e 100644 --- a/renderer/src/ui/settings/drawer.jsx +++ b/renderer/src/ui/settings/drawer.jsx @@ -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) => { diff --git a/renderer/src/ui/settings/group.jsx b/renderer/src/ui/settings/group.jsx index 2c96ac7d..0b98e8f8 100644 --- a/renderer/src/ui/settings/group.jsx +++ b/renderer/src/ui/settings/group.jsx @@ -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 {settings.filter(s => !s.hidden).map((setting) => { diff --git a/renderer/src/ui/settings/title.jsx b/renderer/src/ui/settings/title.jsx index 8cdc9ff2..cd81f6d7 100644 --- a/renderer/src/ui/settings/title.jsx +++ b/renderer/src/ui/settings/title.jsx @@ -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; diff --git a/renderer/src/ui/updater.jsx b/renderer/src/ui/updater.jsx index 500e32ac..79f88b91 100644 --- a/renderer/src/ui/updater.jsx +++ b/renderer/src/ui/updater.jsx @@ -9,50 +9,50 @@ import Checkmark from "./icons/check"; const {useState, useCallback, useEffect} = React; -function CoreUpdaterPanel(props) { +function CoreUpdaterPanel({hasUpdate, remoteVersion, update}) { return - - {!props.hasUpdate &&
} - {props.hasUpdate && } + + {!hasUpdate &&
} + {hasUpdate && }
; } -function NoUpdates(props) { +function NoUpdates({type}) { return
- {Strings.Updater.upToDateBlankslate.format({type: props.type})} + {Strings.Updater.upToDateBlankslate.format({type: type})}
; } -function AddonUpdaterPanel(props) { - const filenames = props.pending; - return props.updateAll(props.type)} : null}> - {!filenames.length && } +function AddonUpdaterPanel({pending, type, updater, update, updateAll}) { + const filenames = pending; + return updateAll(type)} : null}> + {!filenames.length && } {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 - + ; })} ; } -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 [ , - , - , - , + , + , + , ]; } \ No newline at end of file