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