From 5625d63e46c43132676148a86739025c15fa5f2d Mon Sep 17 00:00:00 2001 From: megumin Date: Mon, 17 Oct 2022 20:18:25 +0100 Subject: Settings 2.0 (#107) Co-authored-by: Vendicated --- src/utils/modal.tsx | 91 +++++++++++++++++++++++++++++++++++------------------ src/utils/types.ts | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 31 deletions(-) (limited to 'src/utils') diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx index f142aee..4c8df6c 100644 --- a/src/utils/modal.tsx +++ b/src/utils/modal.tsx @@ -1,15 +1,6 @@ import { filters } from "../webpack"; -import { lazyWebpack } from "./misc"; import { mapMangledModuleLazy } from "../webpack/webpack"; -const ModalRoot = lazyWebpack(filters.byCode("headerIdIsManaged:")); -const Modals = mapMangledModuleLazy("onCloseRequest:null!=", { - openModal: filters.byCode("onCloseRequest:null!="), - closeModal: filters.byCode("onCloseCallback&&") -}); - -let modalId = 1337; - export enum ModalSize { SMALL = "small", MEDIUM = "medium", @@ -17,26 +8,64 @@ export enum ModalSize { DYNAMIC = "dynamic", } -/** - * Open a modal - * @param Component The component to render in the modal - * @returns The key of this modal. This can be used to close the modal later with closeModal - */ -export function openModal(Component: React.ComponentType, modalProps: Record) { - let key = `Vencord${modalId++}`; - Modals.openModal(props => ( - - - - ), { modalKey: key }); - - return key; -} - -/** - * Close a modal by key. The id you need for this is returned by openModal. - * @param key The key of the modal to close - */ -export function closeModal(key: string) { - Modals.closeModal(key); +enum ModalTransitionState { + ENTERING, + ENTERED, + EXITING, + EXITED, + HIDDEN, +} + +export interface ModalProps { + transitionState: ModalTransitionState; + onClose(): Promise; +} + +export interface ModalOptions { + modalKey?: string; + onCloseRequest?: (() => void); + onCloseCallback?: (() => void); +} + +interface ModalRootProps { + transitionState: ModalTransitionState; + children: React.ReactNode; + size?: ModalSize; + role?: "alertdialog" | "dialog"; + className?: string; + onAnimationEnd?(): string; +} + +type RenderFunction = (props: ModalProps) => React.ReactNode; + +export const Modals = mapMangledModuleLazy(".onAnimationEnd,", { + ModalRoot: filters.byCode("headerIdIsManaged:"), + ModalHeader: filters.byCode("children", "separator", "wrap", "NO_WRAP", "grow", "shrink", "id", "header"), + ModalContent: filters.byCode("scrollerRef", "content", "className", "children"), + ModalFooter: filters.byCode("HORIZONTAL_REVERSE", "START", "STRETCH", "NO_WRAP", "footerSeparator"), + ModalCloseButton: filters.byCode("closeWithCircleBackground", "hideOnFullscreen"), +}); + +export const ModalRoot = (props: ModalRootProps) => ; +export const ModalHeader = (props: any) => ; +export const ModalContent = (props: any) => ; +export const ModalFooter = (props: any) => ; +export const ModalCloseButton = (props: any) => ; + +const ModalAPI = mapMangledModuleLazy("onCloseRequest:null!=", { + openModal: filters.byCode("onCloseRequest:null!="), + closeModal: filters.byCode("onCloseCallback&&"), + openModalLazy: m => m?.length === 1 && filters.byCode(".apply(this,arguments)")(m), +}); + +export function openModalLazy(render: () => Promise, options?: ModalOptions & { contextKey?: string; }): Promise { + return ModalAPI.openModalLazy(render, options); +} + +export function openModal(render: RenderFunction, options?: ModalOptions, contextKey?: string): string { + return ModalAPI.openModal(render, options, contextKey); +} + +export function closeModal(modalKey: string, contextKey?: string): void { + return ModalAPI.closeModal(modalKey, contextKey); } diff --git a/src/utils/types.ts b/src/utils/types.ts index f7ccdb6..5ed95e4 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -15,6 +15,7 @@ export interface Patch { find: string; replacement: PatchReplacement | PatchReplacement[]; all?: boolean; + predicate?(): boolean; } export interface PluginAuthor { @@ -34,13 +35,101 @@ interface PluginDef { start?(): void; stop?(): void; patches?: Omit[]; + /** + * List of commands. If you specify these, you must add CommandsAPI to dependencies + */ commands?: Command[]; + /** + * A list of other plugins that your plugin depends on. + * These will automatically be enabled and loaded before your plugin + * Common examples are CommandsAPI, MessageEventsAPI... + */ dependencies?: string[], + /** + * Whether this plugin is required and forcefully enabled + */ required?: boolean; /** * Set this if your plugin only works on Browser or Desktop, not both */ target?: "WEB" | "DESKTOP" | "BOTH"; + /** + * Optionally provide settings that the user can configure in the Plugins tab of settings. + */ + options?: Record; + /** + * Allows you to specify a custom Component that will be rendered in your + * plugin's settings page + */ + settingsAboutComponent?: React.ComponentType; +} + +export enum OptionType { + STRING, + NUMBER, + BIGINT, + BOOLEAN, + SELECT, +} + +export type PluginOptionsItem = + | PluginOptionString + | PluginOptionNumber + | PluginOptionBoolean + | PluginOptionSelect; + +export interface PluginOptionBase { + description: string; + placeholder?: string; + onChange?(newValue: any): void; + disabled?(): boolean; + restartNeeded?: boolean; + componentProps?: Record; + /** + * Set this if the setting only works on Browser or Desktop, not both + */ + target?: "WEB" | "DESKTOP" | "BOTH"; +} + +export interface PluginOptionString extends PluginOptionBase { + type: OptionType.STRING; + /** + * Prevents the user from saving settings if this is false or a string + */ + isValid?(value: string): boolean | string; + default?: string; +} + +export interface PluginOptionNumber extends PluginOptionBase { + type: OptionType.NUMBER | OptionType.BIGINT; + /** + * Prevents the user from saving settings if this is false or a string + */ + isValid?(value: number | BigInt): boolean | string; + default?: number; +} + +export interface PluginOptionBoolean extends PluginOptionBase { + type: OptionType.BOOLEAN; + /** + * Prevents the user from saving settings if this is false or a string + */ + isValid?(value: boolean): boolean | string; + default?: boolean; +} + +export interface PluginOptionSelect extends PluginOptionBase { + type: OptionType.SELECT; + /** + * Prevents the user from saving settings if this is false or a string + */ + isValid?(value: PluginOptionSelectOption): boolean | string; + options: PluginOptionSelectOption[]; +} +export interface PluginOptionSelectOption { + label: string; + value: string | number | boolean; + default?: boolean; } export type IpcRes = { ok: true; value: V; } | { ok: false, error: any; }; -- cgit