diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 2de9362..39fb911 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -302,7 +302,7 @@ export default ErrorBoundary.wrap(function Settings() { changes.add(name)} + onRestartNeeded={name => changes.handleChange(name)} disabled={plugin.required || !!dependency} plugin={plugin} /> diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index e2abff2..f8e7495 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -17,11 +17,10 @@ */ import { useSettings } from "../api/settings"; -import { ChangeList } from "../utils/ChangeList"; import IpcEvents from "../utils/IpcEvents"; import { useAwaiter } from "../utils/misc"; import { downloadSettingsBackup, uploadSettingsBackup } from "../utils/settingsSync"; -import { Alerts, Button, Card, Forms, Margins, Parser, React, Switch } from "../webpack/common"; +import { Button, Card, Forms, Margins, React, Switch } from "../webpack/common"; import DonateButton from "./DonateButton"; import ErrorBoundary from "./ErrorBoundary"; import { Flex } from "./Flex"; @@ -30,27 +29,6 @@ import { handleComponentFailed } from "./handleComponentFailed"; export default ErrorBoundary.wrap(function Settings() { const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke(IpcEvents.GET_SETTINGS_DIR), "Loading..."); const settings = useSettings(); - const changes = React.useMemo(() => new ChangeList(), []); - - React.useEffect(() => { - return () => void (changes.hasChanges && Alerts.show({ - title: "Restart required", - body: ( - <> -

The following plugins require a restart:

-
{changes.map((s, i) => ( - <> - {i > 0 && ", "} - {Parser.parse("`" + s + "`")} - - ))}
- - ), - confirmText: "Restart now", - cancelText: "Later!", - onConfirm: () => location.reload() - })); - }, []); return ( diff --git a/src/utils/Queue.ts b/src/utils/Queue.ts index 6153d40..86eb791 100644 --- a/src/utils/Queue.ts +++ b/src/utils/Queue.ts @@ -18,14 +18,18 @@ import { Promisable } from "type-fest"; +/** + * A queue that can be used to run tasks consecutively. + * Highly recommended for things like fetching data from Discord + */ export class Queue { /** * @param maxSize The maximum amount of functions that can be queued at once. - * If the queue is full, the oldest function will be removed. + * If the queue is full, the oldest function will be removed. */ constructor(public maxSize = Infinity) { } - queue = [] as Array<() => Promisable>; + private queue = [] as Array<() => Promisable>; private promise?: Promise; @@ -34,7 +38,7 @@ export class Queue { if (func) this.promise = Promise.resolve() .then(func) - .then(() => this.next()); + .finally(() => this.next()); else this.promise = undefined; } @@ -44,6 +48,11 @@ export class Queue { this.next(); } + /** + * Append a task at the end of the queue. This task will be executed after all other tasks + * If the queue exceeds the specified maxSize, the first task in queue will be removed. + * @param func Task + */ push(func: () => Promisable) { if (this.size >= this.maxSize) this.queue.shift(); @@ -52,6 +61,11 @@ export class Queue { this.run(); } + /** + * Prepend a task at the beginning of the queue. This task will be executed next + * If the queue exceeds the specified maxSize, the last task in queue will be removed. + * @param func Task + */ unshift(func: () => Promisable) { if (this.size >= this.maxSize) this.queue.pop(); @@ -60,6 +74,9 @@ export class Queue { this.run(); } + /** + * The amount of tasks in the queue + */ get size() { return this.queue.length; } diff --git a/src/utils/debounce.ts b/src/utils/debounce.ts index d9e19de..6e5bba6 100644 --- a/src/utils/debounce.ts +++ b/src/utils/debounce.ts @@ -16,6 +16,13 @@ * along with this program. If not, see . */ +/** + * Returns a new function that will call the wrapped function + * after the specified delay. If the function is called again + * within the delay, the timer will be reset. + * @param func The function to wrap + * @param delay The delay in milliseconds + */ export function debounce(func: T, delay = 300): T { let timeout: NodeJS.Timeout; return function (...args: any[]) { diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx index 44fc819..7389cc2 100644 --- a/src/utils/misc.tsx +++ b/src/utils/misc.tsx @@ -70,6 +70,9 @@ export function useAwaiter(factory: () => Promise, fallbackValue: T | null return [state.value, state.error, state.pending, () => setSignal(signal + 1)]; } +/** + * Returns a function that can be used to force rerender react components + */ export function useForceUpdater() { const [, set] = React.useState(0); return () => set(s => s + 1); @@ -144,6 +147,9 @@ export function classes(...classes: string[]) { return classes.join(" "); } +/** + * Returns a promise that resolves after the specified amount of time + */ export function sleep(ms: number): Promise { return new Promise(r => setTimeout(r, ms)); } diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx index 2affbd7..886e325 100644 --- a/src/utils/modal.tsx +++ b/src/utils/modal.tsx @@ -76,14 +76,26 @@ const ModalAPI = mapMangledModuleLazy("onCloseRequest:null!=", { openModalLazy: m => m?.length === 1 && filters.byCode(".apply(this,arguments)")(m), }); +/** + * Wait for the render promise to resolve, then open a modal with it. + * This is equivalent to render().then(openModal) + * You should use the Modal components exported by this file + */ export function openModalLazy(render: () => Promise, options?: ModalOptions & { contextKey?: string; }): Promise { return ModalAPI.openModalLazy(render, options); } +/** + * Open a Modal with the given render function. + * You should use the Modal components exported by this file + */ export function openModal(render: RenderFunction, options?: ModalOptions, contextKey?: string): string { return ModalAPI.openModal(render, options, contextKey); } +/** + * Close a modal by its key + */ export function closeModal(modalKey: string, contextKey?: string): void { return ModalAPI.closeModal(modalKey, contextKey); } diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 1231d9e..bd06b03 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -42,8 +42,6 @@ export const filters = { ? m => m[props[0]] !== void 0 : m => props.every(p => m[p] !== void 0), - byDisplayName: (deezNuts: string): FilterFn => m => m.default?.displayName === deezNuts, - byCode: (...code: string[]): FilterFn => m => { if (typeof m !== "function") return false; const s = Function.prototype.toString.call(m); @@ -75,6 +73,9 @@ if (IS_DEV && !IS_WEB) { }, 0); } +/** + * Find the first module that matches the filter + */ export const find = traceFunction("find", function find(filter: FilterFn, getDefault = true, isWaitFor = false) { if (typeof filter !== "function") throw new Error("Invalid filter. Expected a function got " + typeof filter); @@ -283,22 +284,31 @@ export function mapMangledModuleLazy(code: string, mappers: Re return proxyLazy(() => mapMangledModule(code, mappers)); } +/** + * Find the first module that has the specified properties + */ export function findByProps(...props: string[]) { return find(filters.byProps(...props)); } +/** + * Find all modules that have the specified properties + */ export function findAllByProps(...props: string[]) { return findAll(filters.byProps(...props)); } +/** + * Find a function by its code + */ export function findByCode(...code: string[]) { return find(filters.byCode(...code)); } -export function findByDisplayName(deezNuts: string) { - return find(filters.byDisplayName(deezNuts)); -} - +/** + * Wait for a module that matches the provided filter to be registered, + * then call the callback with the module as the first argument + */ export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn) { if (typeof filter === "string") filter = filters.byProps(filter);