diff --git a/packages/discord-types/src/components.d.ts b/packages/discord-types/src/components.d.ts index 88884b8d6..a8b181599 100644 --- a/packages/discord-types/src/components.d.ts +++ b/packages/discord-types/src/components.d.ts @@ -192,6 +192,7 @@ export type TextInput = ComponentType, "onChange"> & { onChange(v: string): void; inputRef?: Ref; + autosize?: boolean; }>; export interface SelectOption { diff --git a/packages/discord-types/src/components/Modal.d.ts b/packages/discord-types/src/components/Modal.d.ts new file mode 100644 index 000000000..e8586b647 --- /dev/null +++ b/packages/discord-types/src/components/Modal.d.ts @@ -0,0 +1,136 @@ +import { ComponentType, ReactNode, Ref, RefObject } from "react"; +import { LiteralUnion } from "type-fest"; + +export type ModalActionVariant = LiteralUnion<"primary" | "secondary" | "critical-primary", string>; +export type ModalSize = LiteralUnion<"sm" | "md" | "lg" | "xl" | "xxl", string>; + +export interface ModalAction { + text: string; + variant: ModalActionVariant; + onClick(): void; + loading?: boolean; + disabled?: boolean; +} + +/** + * Modal with all options: https://files.catbox.moe/c8qxt0.png + */ +export interface ModalProps extends RenderModalProps { + size?: ModalSize; + role?: "alertdialog" | "dialog"; + + title: ReactNode; + /** Optional subtitle, will render below title (duh) */ + subtitle?: ReactNode; + + /** Optional Modal content */ + children?: ReactNode; + input?: ReactNode; + preview?: ReactNode; + + listProps?: any; + onScroll?(): void; + scrollerRef?: Ref; + + /** Action buttons at the bottom of the Modal */ + actions?: ModalAction[]; + /** Custom component to show before actions, useful for e.g. "Don't show again" checkbox */ + actionBarInput?: ReactNode; + actionBarInputLayout?: "default" | "chat-input"; + + /** Shows a Notice (card) between (sub-)title and content (children) */ + notice?: { + message: string; + type: LiteralUnion<"critical", string>; + }; +} + +export interface RenderModalProps { + transitionState: number; + onClose(): void; +} + +/** Wrapper around Modal */ +export interface ConfirmModalProps extends ModalProps { + /** Variant for the confirm button, defaults to critical */ + variant?: ModalActionVariant; + confirmText: ModalAction["text"]; + /** Defaults to "Cancel" */ + cancelText?: ModalAction["text"]; + /** + * If onConfirm runs without error, the modal will close. + * To indicate an error, throw an Error. This will show the user a notice inside the Modal and keep it open. + * This notice will either be a generic message if you just throw. If you call setError, it will show that message. + * Even if you call setError, you still have to throw to keep the Modal open + */ + onConfirm?(setError: (error: string) => void): void; + /** Optional callback that runs when the user cancels the action. Whether provided or not, the Modal will close when user clicks Cancel. */ + onCancel?(): void; + onCloseCallback?(): void; + /** Checkbox that shows before the action buttons */ + checkboxProps?: { + /** Defaults to "Don't show again" */ + label?: string; + checked: boolean; + onChange(checked: boolean): void; + }; + + /** Custom Modal content */ + children?: ReactNode; +} + +export type Modal = ComponentType; +export type ConfirmModal = ComponentType; + +export type RenderModal = (props: RenderModalProps) => ReactNode; + +export interface ModalOptions { + modalKey?: string; + onCloseRequest?(): void; + onCloseCallback?(): void; +} + +export interface ModalAPI { + /** + * Wait for the render promise to resolve, then open a modal with it. + * This is usually preferable to render().then(openModal), because it shows a Loading modal + */ + openModalLazy: (renderModal: () => Promise, options?: ModalOptions & { contextKey?: string; }) => Promise; + /** + * Open a Modal with the given render function. + */ + openModal: (renderModal: RenderModal, options?: ModalOptions, contextKey?: string) => string; + /** + * Close a modal by its key + */ + closeModal: (modalKey: string, contextKey?: string) => void; + /** + * Close all open modals + */ + closeAllModals: () => void; +} + +export interface MediaModalItem { + url: string; + type: "IMAGE" | "VIDEO" | "CLIP"; + original?: string; + alt?: string; + width?: number; + height?: number; + animated?: boolean; + maxWidth?: number; + maxHeight?: number; +} + +export interface MediaModalProps { + location?: string; + contextKey?: string; + onCloseCallback?: () => void; + className?: string; + items: MediaModalItem[]; + startingIndex?: number; + onIndexChange?: (...args: any[]) => void; + fit?: string; + shouldRedactExplicitContent?: boolean; + shouldHideMediaOptions?: boolean; +} diff --git a/packages/discord-types/src/index.d.ts b/packages/discord-types/src/index.d.ts index 23ab505d8..83bbba26e 100644 --- a/packages/discord-types/src/index.d.ts +++ b/packages/discord-types/src/index.d.ts @@ -1,5 +1,7 @@ export * from "./common"; +// TODO: Splitting up components into files in the folder would be good export * from "./components"; +export * from "./components/Modal"; export * from "./flux"; export * from "./fluxEvents"; export * from "./menu"; diff --git a/src/api/Notifications/notificationLog.tsx b/src/api/Notifications/notificationLog.tsx index 5b6ef6767..7cfd55a80 100644 --- a/src/api/Notifications/notificationLog.tsx +++ b/src/api/Notifications/notificationLog.tsx @@ -18,12 +18,11 @@ import * as DataStore from "@api/DataStore"; import { Settings } from "@api/Settings"; -import { Flex } from "@components/Flex"; import { openNotificationSettingsModal } from "@components/settings/tabs/vencord/NotificationSettings"; import { classNameFactory } from "@utils/css"; -import { closeModal, ModalCloseButton, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; -import { Alerts, Button, Forms, ListScrollerThin, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; +import { RenderModalProps } from "@vencord/discord-types"; +import { ConfirmModal, Forms, ListScrollerThin, Modal,openModal, React, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; import { nanoid } from "nanoid"; import type { DispatchWithoutAction } from "react"; @@ -130,7 +129,7 @@ function NotificationEntry({ data }: { data: PersistentNotificationData; }) { export function NotificationLog({ log, pending }: { log: PersistentNotificationData[], pending: boolean; }) { if (!log.length && !pending) return ( -
+
No notifications yet @@ -150,56 +149,46 @@ export function NotificationLog({ log, pending }: { log: PersistentNotificationD ); } -function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void; }) { +function LogModal(props: RenderModalProps) { const [log, pending] = useLogs(); return ( - - - Notification Log - - - -
- -
- - - - - - - - -
+ }} + /> + ); + } + } + ]} + > + + ); } export function openNotificationLogModal() { - const key = openModal(modalProps => ( - closeModal(key)} - /> - )); + openModal(props => ); } diff --git a/src/api/Notifications/styles.css b/src/api/Notifications/styles.css index e15e01a7d..1d3085e98 100644 --- a/src/api/Notifications/styles.css +++ b/src/api/Notifications/styles.css @@ -89,12 +89,6 @@ margin-bottom: 40px; } -.vc-notification-log-container { - padding: 1em; - max-height: min(750px, 75vh); - width: 100%; -} - .vc-notification-log-wrapper { height: 120px; width: 100%; diff --git a/src/api/SettingsSync/cloudSetup.tsx b/src/api/SettingsSync/cloudSetup.tsx index 8e77d5fbd..faa8b2ff6 100644 --- a/src/api/SettingsSync/cloudSetup.tsx +++ b/src/api/SettingsSync/cloudSetup.tsx @@ -8,9 +8,8 @@ import * as DataStore from "@api/DataStore"; import { showNotification } from "@api/Notifications"; import { Settings } from "@api/Settings"; import { Logger } from "@utils/Logger"; -import { openModal } from "@utils/modal"; import { relaunch } from "@utils/native"; -import { Alerts, OAuth2AuthorizeModal, UserStore } from "@webpack/common"; +import { ConfirmModal,OAuth2AuthorizeModal, openModal, UserStore } from "@webpack/common"; export const logger = new Logger("SettingsSync:CloudSetup", "#39b7e0"); @@ -29,13 +28,17 @@ export async function checkCloudUrlCsp() { const res = await VencordNative.csp.requestAddOverride(Settings.cloud.url, ["connect-src"], "Cloud Sync"); if (res === "ok") { - Alerts.show({ - title: "Cloud Integration enabled", - body: `${host} has been added to the whitelist. Please restart the app for the changes to take effect.`, - confirmText: "Restart now", - cancelText: "Later!", - onConfirm: relaunch - }); + openModal(props => ( + + )); } return false; } diff --git a/src/components/Card.css b/src/components/Card.css index 74260f37e..80ad3e4fe 100644 --- a/src/components/Card.css +++ b/src/components/Card.css @@ -4,7 +4,7 @@ } .vc-card-normal { - border: 1px var(--border-subtle); + border: 1px solid var(--border-subtle); } .vc-card-warning { diff --git a/src/components/settings/tabs/BaseTab.tsx b/src/components/settings/tabs/BaseTab.tsx index 17a2e8025..79aef5890 100644 --- a/src/components/settings/tabs/BaseTab.tsx +++ b/src/components/settings/tabs/BaseTab.tsx @@ -16,11 +16,10 @@ * along with this program. If not, see . */ -import { BaseText } from "@components/BaseText"; import ErrorBoundary from "@components/ErrorBoundary"; import { handleComponentFailed } from "@components/handleComponentFailed"; -import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { onlyOnce } from "@utils/onlyOnce"; +import { Modal,openModal } from "@webpack/common"; import type { ComponentType, PropsWithChildren } from "react"; export function SettingsTab({ children }: PropsWithChildren) { @@ -42,19 +41,18 @@ export function wrapTab(component: ComponentType, tab: string) { } export function openSettingsTabModal(Tab: ComponentType) { - try { - openModal(wrapTab((modalProps: ModalProps) => ( - - - {Tab.displayName?.replace("SettingsTab", "") || "Settings"} - - + Tab = wrapTab(Tab, Tab.displayName || "SettingsTab"); - - - - - ), Tab.displayName || "SettingsTab")); + try { + openModal(props => ( + + + + )); } catch { handleSettingsTabError(); } diff --git a/src/components/settings/tabs/plugins/ContributorModal.css b/src/components/settings/tabs/plugins/ContributorModal.css index ef3b2ddec..fd40b3888 100644 --- a/src/components/settings/tabs/plugins/ContributorModal.css +++ b/src/components/settings/tabs/plugins/ContributorModal.css @@ -1,13 +1,3 @@ -.vc-author-modal-root { - padding: 1em; -} - -.vc-author-modal-header { - display: flex; - align-items: center; - margin-bottom: 1em; -} - .vc-author-modal-name { text-transform: none; flex-grow: 0; diff --git a/src/components/settings/tabs/plugins/ContributorModal.tsx b/src/components/settings/tabs/plugins/ContributorModal.tsx index 31ed77f32..9139fe8f3 100644 --- a/src/components/settings/tabs/plugins/ContributorModal.tsx +++ b/src/components/settings/tabs/plugins/ContributorModal.tsx @@ -7,15 +7,13 @@ import "./ContributorModal.css"; import { useSettings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Link } from "@components/Link"; import { DevsById } from "@utils/constants"; import { classNameFactory } from "@utils/css"; import { fetchUserProfile } from "@utils/discord"; import { classes, pluralise } from "@utils/misc"; -import { ModalContent, ModalRoot, openModal } from "@utils/modal"; -import { User } from "@vencord/discord-types"; -import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common"; +import { RenderModalProps, User } from "@vencord/discord-types"; +import { Forms, Modal,openModal, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common"; import Plugins from "~plugins"; @@ -25,18 +23,10 @@ import { PluginCard } from "./PluginCard"; const cl = classNameFactory("vc-author-modal-"); export function openContributorModal(user: User) { - openModal(modalProps => - - - - - - - - ); + openModal(modalProps => ); } -function ContributorModal({ user }: { user: User; }) { +function ContributorModal({ user, modalProps }: { user: User; modalProps: RenderModalProps; }) { useSettings(); const profile = useStateFromStores([UserProfileStore], () => UserProfileStore.getUserProfile(user.id)); @@ -63,41 +53,47 @@ function ContributorModal({ user }: { user: User; }) { const ContributedHyperLink = contributed; return ( - <> -
- - {user.username} + + + {user.username} -
- {website && ( - - )} - {githubName && ( - - )} +
+ {website && ( + + )} + {githubName && ( + + )} +
-
- - {plugins.length ? ( - - This person has {ContributedHyperLink} to {pluralise(plugins.length, "plugin")}! - - ) : ( - - This person has not made any plugins. They likely {ContributedHyperLink} to Vencord in other ways! - - )} - + } + subtitle={ + plugins.length + ? ( + + This person has {ContributedHyperLink} to {pluralise(plugins.length, "plugin")}! + + ) + : ( + + This person has not made any plugins. They likely {ContributedHyperLink} to Vencord in other ways! + + ) + } + > {!!plugins.length && (
{plugins.map(p => @@ -110,6 +106,6 @@ function ContributorModal({ user }: { user: User; }) { )}
)} - + ); } diff --git a/src/components/settings/tabs/plugins/LinkIconButton.css b/src/components/settings/tabs/plugins/LinkIconButton.css index 97db07802..835206381 100644 --- a/src/components/settings/tabs/plugins/LinkIconButton.css +++ b/src/components/settings/tabs/plugins/LinkIconButton.css @@ -9,4 +9,5 @@ .vc-settings-modal-links { display: flex; gap: 0.2em; + margin-left: auto; } \ No newline at end of file diff --git a/src/components/settings/tabs/plugins/PluginModal.css b/src/components/settings/tabs/plugins/PluginModal.css index 085b2bab6..19175fb02 100644 --- a/src/components/settings/tabs/plugins/PluginModal.css +++ b/src/components/settings/tabs/plugins/PluginModal.css @@ -1,5 +1,5 @@ -.vc-settings-modal-content { - padding-bottom: 24px; +.vc-plugin-modal-header { + display: flex; } .vc-plugin-modal-info { @@ -13,7 +13,7 @@ display: flex; flex-wrap: wrap; gap: 4px; - margin-block: 8px 16px; + margin-top: 8px; } .vc-plugin-modal-tag { diff --git a/src/components/settings/tabs/plugins/PluginModal.tsx b/src/components/settings/tabs/plugins/PluginModal.tsx index bd5d8e98d..0e0422c9a 100644 --- a/src/components/settings/tabs/plugins/PluginModal.tsx +++ b/src/components/settings/tabs/plugins/PluginModal.tsx @@ -21,6 +21,7 @@ import "./PluginModal.css"; import { generateId } from "@api/Commands"; import { hasAnyVisibleSettings, isSettingHidden } from "@api/PluginManager"; import { useSettings } from "@api/Settings"; +import { BaseText } from "@components/BaseText"; import ErrorBoundary from "@components/ErrorBoundary"; import { debounce } from "@shared/debounce"; import { gitRemote } from "@shared/vencordUserAgent"; @@ -28,11 +29,10 @@ import { classNameFactory } from "@utils/css"; import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; -import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin, PluginTag } from "@utils/types"; -import { User } from "@vencord/discord-types"; +import { RenderModalProps, User } from "@vencord/discord-types"; import { findCssClassesLazy } from "@webpack"; -import { Clickable, FluxDispatcher, Forms, React, Text, Tooltip, useEffect, useMemo, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common"; +import { Clickable, FluxDispatcher, Forms, Modal,openModal, React, Text, Tooltip, useEffect, useMemo, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common"; import { Constructor } from "type-fest"; import { PluginMeta } from "~plugins"; @@ -46,7 +46,7 @@ const cl = classNameFactory("vc-plugin-modal-"); const AvatarStyles = findCssClassesLazy("moreUsers", "avatar", "clickableAvatar"); const UserRecord: Constructor> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; -interface PluginModalProps extends ModalProps { +interface PluginModalProps extends RenderModalProps { plugin: Plugin; onRestartNeeded(key: string): void; } @@ -131,6 +131,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti onChange={debounce(onChange)} pluginSettings={pluginSettings} definedSettings={settings} + closePluginSettings={onClose} /> ); @@ -166,32 +167,38 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti const pluginMeta = PluginMeta[plugin.name]; return ( - - - {plugin.name} - - - - -
-
-
- {plugin.description} - {!!plugin.tags?.length && } + + {plugin.name} + {!pluginMeta.userPlugin && ( +
+ +
- {!pluginMeta.userPlugin && ( -
- - -
- )} + )} +
+ } + subtitle={ +
+
+ {plugin.description} + {!!plugin.tags?.length && }
+
+ } + > +
+
Authors
@@ -234,8 +241,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti Settings {renderSettings()}
- - +
+ ); } diff --git a/src/components/settings/tabs/plugins/UIElements.css b/src/components/settings/tabs/plugins/UIElements.css index 30db51dbd..a362aad6a 100644 --- a/src/components/settings/tabs/plugins/UIElements.css +++ b/src/components/settings/tabs/plugins/UIElements.css @@ -18,10 +18,9 @@ } .vc-plugin-ui-elements-modal-content { - padding: 1em; display: flex; flex-direction: column; - gap: 1.5em; + gap: 2em; } .vc-plugin-ui-elements-switches { diff --git a/src/components/settings/tabs/plugins/UIElements.tsx b/src/components/settings/tabs/plugins/UIElements.tsx index 8d2b26fc8..ba33fe43d 100644 --- a/src/components/settings/tabs/plugins/UIElements.tsx +++ b/src/components/settings/tabs/plugins/UIElements.tsx @@ -17,9 +17,9 @@ import { Switch } from "@components/Switch"; import { classNameFactory } from "@utils/css"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; -import { ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { IconComponent } from "@utils/types"; -import { Clickable } from "@webpack/common"; +import { RenderModalProps } from "@vencord/discord-types"; +import { Clickable, Modal,openModal } from "@webpack/common"; const cl = classNameFactory("vc-plugin-ui-elements-"); @@ -75,7 +75,7 @@ function Section(props: { return (
- {title} + {title} {description}
@@ -90,12 +90,12 @@ function Section(props: { ); } -function UIElementsModal(props: ModalProps) { +function UIElementsModal(props: RenderModalProps) { const { uiElements } = useSettings(["uiElements.*"]); return ( - - + +
- - +
+
); } diff --git a/src/components/settings/tabs/plugins/components/Common.tsx b/src/components/settings/tabs/plugins/components/Common.tsx index ea947b8cf..de7486457 100644 --- a/src/components/settings/tabs/plugins/components/Common.tsx +++ b/src/components/settings/tabs/plugins/components/Common.tsx @@ -22,6 +22,7 @@ interface SettingBaseProps { }; id: string; definedSettings: DefinedSettings; + closePluginSettings(): void; } export type SettingProps = SettingBaseProps; diff --git a/src/components/settings/tabs/plugins/components/ComponentSetting.tsx b/src/components/settings/tabs/plugins/components/ComponentSetting.tsx index e1521f02a..d67f83be7 100644 --- a/src/components/settings/tabs/plugins/components/ComponentSetting.tsx +++ b/src/components/settings/tabs/plugins/components/ComponentSetting.tsx @@ -20,6 +20,6 @@ import { PluginSettingComponentDef } from "@utils/types"; import { ComponentSettingProps } from "./Common"; -export function ComponentSetting({ setting, onChange }: ComponentSettingProps) { - return setting.component({ setValue: onChange, option: setting }); +export function ComponentSetting({ setting, onChange, closePluginSettings }: ComponentSettingProps) { + return setting.component({ setValue: onChange, option: setting, closePluginSettings }); } diff --git a/src/components/settings/tabs/plugins/index.tsx b/src/components/settings/tabs/plugins/index.tsx index 5a70ed997..99a327bd2 100644 --- a/src/components/settings/tabs/plugins/index.tsx +++ b/src/components/settings/tabs/plugins/index.tsx @@ -35,7 +35,7 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { useAwaiter, useCleanupEffect } from "@utils/react"; import { PluginTag, PluginTags } from "@utils/types"; -import { Alerts, Button, lodash, Parser, React, SearchableSelect, Select, TextInput, Tooltip, useMemo, useRef, useState } from "@webpack/common"; +import { Button, ConfirmModal,lodash, openModal, Parser, React, SearchableSelect, Select, TextInput, Tooltip, useMemo, useRef, useState } from "@webpack/common"; import { JSX } from "react"; import Plugins, { ExcludedPlugins, PluginMeta } from "~plugins"; @@ -121,9 +121,15 @@ function PluginSettings() { useCleanupEffect(() => { if (changes.hasChanges) - Alerts.show({ - title: "Restart required", - body: ( + openModal(props => ( + location.reload()} + > <>

The following plugins require a restart:

{changes.map((s, i) => ( @@ -133,11 +139,8 @@ function PluginSettings() { ))}
- ), - confirmText: "Restart now", - cancelText: "Later!", - onConfirm: () => location.reload() - }); +
+ )); }, []); const depMap = useMemo(() => { diff --git a/src/components/settings/tabs/styles.css b/src/components/settings/tabs/styles.css index fd4aa8f46..6985a6ff7 100644 --- a/src/components/settings/tabs/styles.css +++ b/src/components/settings/tabs/styles.css @@ -59,12 +59,4 @@ .vc-cloud-button-icon { height: 1.25em; -} - -.vc-settings-modal-root { - background: var(--background-base-low); -} - -.vc-settings-modal { - padding-bottom: 1em; } \ No newline at end of file diff --git a/src/components/settings/tabs/sync/CloudTab.tsx b/src/components/settings/tabs/sync/CloudTab.tsx index 19bf33de3..114b16def 100644 --- a/src/components/settings/tabs/sync/CloudTab.tsx +++ b/src/components/settings/tabs/sync/CloudTab.tsx @@ -34,7 +34,7 @@ import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { IconComponent } from "@utils/types"; -import { Alerts, Select, Tooltip, useState } from "@webpack/common"; +import { ConfirmModal,openModal, Select, Tooltip, useState } from "@webpack/common"; function validateUrl(url: string) { try { @@ -228,14 +228,16 @@ function ResetSection() { Alerts.show({ - title: "Are you sure?", - body: "Once your data is erased, we cannot recover it. There's no going back!", - onConfirm: eraseAllCloudData, - confirmText: "Erase it!", - confirmColor: "vc-cloud-erase-data-danger-btn", - cancelText: "Nevermind" - })} + onClick={() => openModal(props => ( + + ))} Icon={DeleteIcon} > Delete your Cloud Account diff --git a/src/components/settings/tabs/themes/CspErrorCard.tsx b/src/components/settings/tabs/themes/CspErrorCard.tsx index 685b9a91a..9e3b89c67 100644 --- a/src/components/settings/tabs/themes/CspErrorCard.tsx +++ b/src/components/settings/tabs/themes/CspErrorCard.tsx @@ -12,7 +12,7 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { relaunch } from "@utils/native"; import { useForceUpdater } from "@utils/react"; -import { Alerts, Button, Forms } from "@webpack/common"; +import { Button, ConfirmModal, Forms, openModal } from "@webpack/common"; export function CspErrorCard() { if (IS_WEB) return null; @@ -38,19 +38,23 @@ export function CspErrorCard() { forceUpdate(); - Alerts.show({ - title: "Restart Required", - body: "A restart is required to apply this change", - confirmText: "Restart now", - cancelText: "Later!", - onConfirm: relaunch - }); + openModal(props => ( + + )); }; const hasImgurHtmlDomain = errors.some(isImgurHtmlDomain); return ( - + Blocked Resources Some images, styles, or fonts were blocked because they come from disallowed domains. It is highly recommended to move them to GitHub or Imgur. But you may also allow domains if you fully trust them. diff --git a/src/components/settings/tabs/themes/index.tsx b/src/components/settings/tabs/themes/index.tsx index 2bc5e81eb..4a44b7057 100644 --- a/src/components/settings/tabs/themes/index.tsx +++ b/src/components/settings/tabs/themes/index.tsx @@ -18,8 +18,12 @@ import "./styles.css"; +import { BaseText } from "@components/BaseText"; import { Card } from "@components/Card"; +import { Flex } from "@components/Flex"; import { Link } from "@components/Link"; +import { Margins } from "@components/margins"; +import { Paragraph } from "@components/Paragraph"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { getStylusWebStoreUrl } from "@utils/web"; import { Forms, React, TabBar, useState } from "@webpack/common"; @@ -59,10 +63,20 @@ function ThemesTab() { - + + - {currentTab === ThemeTab.LOCAL && } - {currentTab === ThemeTab.ONLINE && } + + Theme Performance + + Themes and custom CSS have the potential to cause major lag! If you experience performance issues, try + disabling your themes and CSS to see if they're the cause. The most common cause of lag is the :has() operator. + + + + {currentTab === ThemeTab.LOCAL && } + {currentTab === ThemeTab.ONLINE && } + ); } diff --git a/src/components/settings/tabs/updater/Components.tsx b/src/components/settings/tabs/updater/Components.tsx index 33ea34d7a..67c3b443d 100644 --- a/src/components/settings/tabs/updater/Components.tsx +++ b/src/components/settings/tabs/updater/Components.tsx @@ -12,7 +12,7 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { relaunch } from "@utils/native"; import { changes, checkForUpdates, update, updateError } from "@utils/updater"; -import { Alerts, Button, Forms, React, Toasts, useState } from "@webpack/common"; +import { Button, ConfirmModal,Forms, openModal, React, Toasts, useState } from "@webpack/common"; import { runWithDispatch } from "./runWithDispatch"; @@ -100,17 +100,21 @@ export function Updatable(props: CommonProps) { setUpdates([]); await new Promise(r => { - Alerts.show({ - title: "Update Success!", - body: "Successfully updated. Restart now to apply the changes?", - confirmText: "Restart", - cancelText: "Not now!", - onConfirm() { - relaunch(); - r(); - }, - onCancel: r - }); + openModal(props => ( + { + relaunch(); + r(); + }} + onCancel={r} + /> + )); }); } })} diff --git a/src/components/settings/tabs/updater/runWithDispatch.tsx b/src/components/settings/tabs/updater/runWithDispatch.tsx index 719958e78..55f290ad9 100644 --- a/src/components/settings/tabs/updater/runWithDispatch.tsx +++ b/src/components/settings/tabs/updater/runWithDispatch.tsx @@ -6,7 +6,7 @@ import { ErrorCard } from "@components/ErrorCard"; import { UpdateLogger } from "@utils/updater"; -import { Alerts, Parser } from "@webpack/common"; +import { ConfirmModal,openModal, Parser } from "@webpack/common"; function getErrorMessage(e: any) { if (!e?.code || !e.cmd) @@ -33,16 +33,20 @@ export function runWithDispatch(dispatch: React.Dispatch ( + {err.split("\n").map((line, idx) =>
{Parser.parse(line)}
)}
- ) - }); +
+ )); } finally { dispatch(false); } diff --git a/src/components/settings/tabs/vencord/NotificationSettings.tsx b/src/components/settings/tabs/vencord/NotificationSettings.tsx index 9024c42bb..f8de31ca0 100644 --- a/src/components/settings/tabs/vencord/NotificationSettings.tsx +++ b/src/components/settings/tabs/vencord/NotificationSettings.tsx @@ -10,8 +10,7 @@ import { ErrorCard } from "@components/ErrorCard"; import { Flex } from "@components/Flex"; import { Margins } from "@utils/margins"; import { identity } from "@utils/misc"; -import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { Button, Forms, Select, Slider, Text } from "@webpack/common"; +import { Button, Forms, Modal,openModal, Select, Slider } from "@webpack/common"; export function NotificationSection() { return ( @@ -35,16 +34,13 @@ export function NotificationSection() { export function openNotificationSettingsModal() { openModal(props => ( - - - Notification Settings - - - - - - - + + + )); } @@ -52,7 +48,7 @@ function NotificationSettings() { const settings = useSettings(["notifications.*"]).notifications; return ( -
+ <> Notification Style {settings.useNative !== "never" && Notification?.permission === "denied" && ( @@ -122,6 +118,6 @@ function NotificationSettings() { onValueRender={v => v === 200 ? "∞" : v} onMarkerRender={v => v === 200 ? "∞" : v} /> -
+ ); } diff --git a/src/components/settings/tabs/vencord/index.tsx b/src/components/settings/tabs/vencord/index.tsx index 4459d8760..7622d67e0 100644 --- a/src/components/settings/tabs/vencord/index.tsx +++ b/src/components/settings/tabs/vencord/index.tsx @@ -32,7 +32,7 @@ import { IS_MAC, IS_WINDOWS } from "@utils/constants"; import { Margins } from "@utils/margins"; import { isPluginDev } from "@utils/misc"; import { relaunch } from "@utils/native"; -import { Alerts, Forms, React, useMemo, UserStore } from "@webpack/common"; +import { ConfirmModal,Forms, openModal, React, useMemo, UserStore } from "@webpack/common"; import { DonateButtonComponent, isDonor } from "./DonateButton"; import { VibrancySettings } from "./MacVibrancySettings"; @@ -111,13 +111,17 @@ function Switches() { settings[key] = v; if (restartRequired) { - Alerts.show({ - title: "Restart Required", - body: "A restart is required to apply this change", - confirmText: "Restart now", - cancelText: "Later!", - onConfirm: relaunch - }); + openModal(props => ( + + )); } }} /> diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index a00b47501..6e79def0e 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -29,9 +29,8 @@ import { copyWithToast } from "@utils/discord"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { shouldShowContributorBadge } from "@utils/misc"; -import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import definePlugin from "@utils/types"; -import { ContextMenuApi, Forms, Menu, Toasts, UserStore } from "@webpack/common"; +import { ContextMenuApi, Forms, Menu, Modal,openModal, Toasts, UserStore } from "@webpack/common"; const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64"; @@ -190,13 +189,14 @@ export default definePlugin({ ContextMenuApi.openContextMenu(event, () => ); }, onClick() { - const modalKey = openModal(props => ( + openModal(props => ( { - closeModal(modalKey); + props.onClose(); VencordNative.native.openExternal("https://github.com/sponsors/Vendicated"); }}> - - + - - + } + > +
-
- +
+
- - +
+ )); }, diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 46666b24e..05484a248 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -34,8 +34,8 @@ import { onlyOnce } from "@utils/onlyOnce"; import { makeCodeblock } from "@utils/text"; import definePlugin from "@utils/types"; import { checkForUpdates, isOutdated, update } from "@utils/updater"; -import { Channel } from "@vencord/discord-types"; -import { Alerts, Button, ChannelStore, Forms, GuildMemberStore, Parser, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; +import { Channel, RenderModalProps } from "@vencord/discord-types"; +import { Button, ChannelStore, ConfirmModal, Forms, GuildMemberStore, openModal, Parser, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; import { JSX } from "react"; import gitHash from "~git-hash"; @@ -136,6 +136,34 @@ const settings = definePluginSettings({}).withPrivateSettings<{ dismissedDevBuildWarning?: boolean; }>(); +function DevBuildConfirmModal(props: RenderModalProps) { + const s = settings.use(["dismissedDevBuildWarning"]); + + return ( + s.dismissedDevBuildWarning = checked + }} + > +
+ You are using a custom build of Vencord, which we do not provide support for! + + + We only provide support for official builds. + Either switch to an official build or figure your issue out yourself. + + + You will be banned from receiving support if you ignore this rule. +
+
+ ); +} + export default definePlugin({ name: "SupportHelper", required: true, @@ -180,20 +208,28 @@ export default definePlugin({ await checkForUpdatesOnce().catch(() => { }); if (isOutdated) { - return Alerts.show({ - title: "Hold on!", - body:
- You are using an outdated version of Vencord! Chances are, your issue is already fixed. - - Please first update before asking for support! - -
, - onCancel: () => openSettingsTabModal(UpdaterTab!), - cancelText: "View Updates", - confirmText: "Update & Restart Now", - onConfirm: forceUpdate, - secondaryConfirmText: "I know what I'm doing or I can't update" - }); + openModal(props => ( + openSettingsTabModal(UpdaterTab!)} + > +
+ You are using an outdated version of Vencord! Chances are, your issue is already fixed. + + Please first update before asking for support! + + + If you know what you're doing or cannot update, you can dismiss this prompt. + +
+
+ )); + return; } } @@ -201,35 +237,28 @@ export default definePlugin({ if (!roles || TrustedRolesIds.some(id => roles.includes(id))) return; if (!IS_WEB && IS_UPDATER_DISABLED) { - return Alerts.show({ - title: "Hold on!", - body:
- You are using an externally updated Vencord version, which we do not provide support for! - - Please either switch to an officially supported version of Vencord, or - contact your package maintainer for support instead. - -
- }); + openModal(props => ( + +
+ You are using an externally updated Vencord version, which we do not provide support for! + + Please either switch to an officially supported version of Vencord, or + contact your package maintainer for support instead. + +
+
+ )); + return; } if (!IS_STANDALONE && !settings.store.dismissedDevBuildWarning) { - return Alerts.show({ - title: "Hold on!", - body:
- You are using a custom build of Vencord, which we do not provide support for! - - - We only provide support for official builds. - Either switch to an official build or figure your issue out yourself. - - - You will be banned from receiving support if you ignore this rule. -
, - confirmText: "Understood", - secondaryConfirmText: "Don't show again", - onConfirmSecondary: () => settings.store.dismissedDevBuildWarning = true - }); + openModal(props => ); + return; } } }, diff --git a/src/plugins/betterSessions/components/RenameButton.tsx b/src/plugins/betterSessions/components/RenameButton.tsx index c1c24d2e2..b20649d06 100644 --- a/src/plugins/betterSessions/components/RenameButton.tsx +++ b/src/plugins/betterSessions/components/RenameButton.tsx @@ -6,7 +6,7 @@ import { Button } from "@components/Button"; import { SessionInfo } from "@plugins/betterSessions/types"; -import { openModal } from "@utils/modal"; +import { openModal } from "@webpack/common"; import { RenameModal } from "./RenameModal"; diff --git a/src/plugins/betterSessions/components/RenameModal.tsx b/src/plugins/betterSessions/components/RenameModal.tsx index e3faa2091..718a0ad3d 100644 --- a/src/plugins/betterSessions/components/RenameModal.tsx +++ b/src/plugins/betterSessions/components/RenameModal.tsx @@ -16,13 +16,14 @@ * along with this program. If not, see . */ +import { TextButton } from "@components/Button"; import { SessionInfo } from "@plugins/betterSessions/types"; import { getDefaultName, savedSessionsCache, saveSessionsToDataStore } from "@plugins/betterSessions/utils"; -import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal"; -import { Button, Forms, React, TextInput } from "@webpack/common"; +import { RenderModalProps } from "@vencord/discord-types"; +import { Forms, Modal,React, TextInput } from "@webpack/common"; import { KeyboardEvent } from "react"; -export function RenameModal({ props, session, state }: { props: ModalProps, session: SessionInfo["session"], state: [string, React.Dispatch>]; }) { +export function RenameModal({ props, session, state }: { props: RenderModalProps, session: SessionInfo["session"], state: [string, React.Dispatch>]; }) { const [title, setTitle] = state; const [value, setValue] = React.useState(savedSessionsCache.get(session.id_hash)?.name ?? ""); @@ -39,13 +40,24 @@ export function RenameModal({ props, session, state }: { props: ModalProps, sess } return ( - - - Rename - - - - New device name + props.onClose() + }, + { + text: "Save", + variant: "primary", + onClick: onSaveClick + } + ]} + > +
+ New device name - - - - -
- - -
-
- + +
+
); } diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index 51391b0b9..c9ae2a8a5 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -22,7 +22,6 @@ import { getCurrentChannel, getCurrentGuild } from "@utils/discord"; import { runtimeHashMessageKey } from "@utils/intlHash"; import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy"; import { sleep } from "@utils/misc"; -import { ModalAPI } from "@utils/modal"; import { relaunch } from "@utils/native"; import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches"; import definePlugin, { PluginNative, StartAt } from "@utils/types"; @@ -176,8 +175,8 @@ function makeShortcuts() { me: { getter: () => Common.UserStore.getCurrentUser(), preload: false }, meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false }, messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false }, - openModal: { getter: () => ModalAPI.openModal }, - openModalLazy: { getter: () => ModalAPI.openModalLazy }, + openModal: { getter: () => Common.openModal }, + openModalLazy: { getter: () => Common.openModalLazy }, Stores: { getter: () => Object.fromEntries(Webpack.fluxStores) }, diff --git a/src/plugins/crashHandler/index.ts b/src/plugins/crashHandler/index.ts index 976b29865..2b82faf68 100644 --- a/src/plugins/crashHandler/index.ts +++ b/src/plugins/crashHandler/index.ts @@ -21,11 +21,10 @@ import { showNotification } from "@api/Notifications"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; -import { closeAllModals } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; import { maybePromptToUpdate } from "@utils/updater"; import { filters, findBulk, proxyLazyWebpack } from "@webpack"; -import { DraftType, ExpressionPickerStore, FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common"; +import { closeAllModals, DraftType, ExpressionPickerStore, FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common"; const CrashHandlerLogger = new Logger("CrashHandler"); diff --git a/src/plugins/customCommands/CreateTagModal.tsx b/src/plugins/customCommands/CreateTagModal.tsx index 652f18503..6c39d154a 100644 --- a/src/plugins/customCommands/CreateTagModal.tsx +++ b/src/plugins/customCommands/CreateTagModal.tsx @@ -4,130 +4,116 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { BaseText } from "@components/BaseText"; -import { Button } from "@components/Button"; -import { Card } from "@components/Card"; import { InlineCode } from "@components/CodeBlock"; import { ExpandableSection } from "@components/ExpandableCard"; import { Flex } from "@components/Flex"; import { HeadingSecondary } from "@components/Heading"; import { InfoIcon } from "@components/Icons"; -import { Margins } from "@components/margins"; import { Paragraph } from "@components/Paragraph"; -import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { TextArea, TextInput, useState } from "@webpack/common"; +import { RenderModalProps } from "@vencord/discord-types"; +import { Modal,openModal, TextArea, TextInput, useState } from "@webpack/common"; import { parseTagArguments } from "."; import { addTag, getTag, Tag } from "./settings"; export function openCreateTagModal(initialValue: Tag = { name: "", message: "" }) { openModal(modalProps => ( - + )); } const EXAMPLE_RESPONSE = "Hello {{user}}! I am feeling {{mood = great}}."; -function Modal({ initialValue, modalProps }: { initialValue: Tag; modalProps: ModalProps; }) { +function CreateTagDialog({ initialValue, modalProps }: { initialValue: Tag; modalProps: RenderModalProps; }) { const [name, setName] = useState(initialValue.name); const [message, setMessage] = useState(initialValue.message.replaceAll("\\n", "\n")); + const isEdit = Boolean(initialValue.name); + const detectedArguments = parseTagArguments(message); const hasReservedEphemeral = detectedArguments.some(arg => arg.name === "ephemeral"); const nameAlreadyExists = name !== initialValue.name && getTag(name); + const notice = hasReservedEphemeral + ? 'The argument name "ephemeral" is reserved and cannot be used.' + : nameAlreadyExists + ? `A tag with the name "${name}" already exists and will be overwritten.` + : undefined; + return ( - - - Create Tag - - + { + const tag = { name, message }; + addTag(tag); + modalProps.onClose(); + }, + disabled: !name || !message || hasReservedEphemeral + } + ]} + notice={notice ? { message: notice, type: "critical" } : undefined} + > + +
+ Name + +
- - - Create a new tag which will be registered as a slash command. - -
- Name - -
+
+ Response +