migrate and fix a bunch of test issues because of deprecations

This commit is contained in:
thororen1234 2026-05-18 11:11:21 -04:00
parent b7ed0728c8
commit 1924684544
No known key found for this signature in database
84 changed files with 2106 additions and 2336 deletions

View file

@ -37,7 +37,6 @@ import { classNameFactory } from "@utils/css";
import { copyWithToast, openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { openModal } from "@utils/modal";
import { relaunch, showItemInFolder } from "@utils/native";
import { useAwaiter, useForceUpdater } from "@utils/react";
import type { ThemeHeader } from "@utils/themes";
@ -45,7 +44,7 @@ import { getThemeInfo, stripBOM, type UserThemeHeader } from "@utils/themes/bd";
import { usercssParse } from "@utils/themes/usercss";
import { getStylusWebStoreUrl } from "@utils/web";
import { findComponentByCodeLazy, findLazy } from "@webpack";
import { Alerts, Menu, React, Select, showToast, TextInput, Toasts, Tooltip, useEffect, useMemo, useState } from "@webpack/common";
import { Alerts, Menu, openModal,React, Select, showToast, TextInput, Toasts, Tooltip, useEffect, useMemo, useState } from "@webpack/common";
import { ContextMenuApi } from "@webpack/common/menu";
import type { ComponentType, Ref, SyntheticEvent } from "react";
import type { UserstyleHeader } from "usercss-meta";

View file

@ -9,9 +9,8 @@ import { BaseText } from "@components/BaseText";
import { Flex } from "@components/Flex";
import { CopyIcon, PasteIcon, ResetIcon } from "@components/Icons";
import { copyWithToast } from "@utils/discord";
import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot } from "@utils/modal";
import { RenderModalProps } from "@vencord/discord-types";
import { showToast, Toasts, Tooltip } from "@webpack/common";
import { Modal, showToast, Toasts, Tooltip } from "@webpack/common";
import { type ReactNode } from "react";
import { UserstyleHeader } from "usercss-meta";
@ -187,21 +186,22 @@ export function UserCSSSettingsModal({ modalProps, theme, onSettingsReset }: Use
}
return (
<ModalRoot {...modalProps}>
<ModalHeader separator={false}>
<BaseText size="lg" weight="semibold" style={{ flexGrow: 1 }}>Settings for {theme.name}</BaseText>
<Flex style={{ gap: 4, marginRight: 4 }} className="vc-settings-usercss-ie-buttons">
<ExportButton themeSettings={themeVars} />
<ImportButton themeSettings={themeVars} />
<ResetButton themeSettings={userCssVars} themeId={theme.id} close={modalProps.onClose} onReset={onSettingsReset} />
<Modal
{...modalProps}
title={
<Flex style={{ alignItems: "center", gap: 4 }}>
<BaseText size="lg" weight="semibold" style={{ flexGrow: 1 }}>Settings for {theme.name}</BaseText>
<Flex style={{ gap: 4, marginRight: 4 }} className="vc-settings-usercss-ie-buttons">
<ExportButton themeSettings={themeVars} />
<ImportButton themeSettings={themeVars} />
<ResetButton themeSettings={userCssVars} themeId={theme.id} close={modalProps.onClose} onReset={onSettingsReset} />
</Flex>
</Flex>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent>
<Flex flexDirection="column" style={{ gap: 12, marginBottom: 16 }}>
{controls}
</Flex>
</ModalContent>
</ModalRoot>
}
>
<Flex flexDirection="column" style={{ gap: 12, marginBottom: 16 }}>
{controls}
</Flex>
</Modal>
);
}

View file

@ -1,12 +1,12 @@
.vc-author-modal-root {
padding: 1em;
padding: 0 0 16px;
}
.vc-author-modal-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 1em;
margin-bottom: 4px;
}
.vc-author-modal-name {
@ -50,18 +50,21 @@
0 2px 6px rgb(0 0 0 / 35%);
}
.vc-author-modal-root + [class*="footer"] {
padding: 16px;
div:has(.vc-author-modal-root) > [class*="actionBar"] {
padding: 8px 16px;
min-height: unset;
}
.vc-author-modal-links {
display: flex;
gap: 4px;
margin-left: auto;
width: 100%;
justify-content: flex-end;
}
.vc-author-modal-plugins {
display: grid;
gap: 0.5em;
margin-top: 0.75em;
}
}

View file

@ -13,7 +13,7 @@ import { Paragraph } from "@components/Paragraph";
import { EquicordDevsById, VencordDevsById } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { fetchUserProfile } from "@utils/discord";
import { classes, pluralise } from "@utils/misc";
import { pluralise } from "@utils/misc";
import { RenderModalProps, User } from "@vencord/discord-types";
import { Modal, openModal, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
@ -63,7 +63,7 @@ function ContributorModal({ user, modalProps }: { user: User; modalProps: Render
<Modal
{...modalProps}
title={
<div className="vc-plugin-modal-header">
<div className={cl("header")}>
<img
className={cl("avatar")}
src={user.getAvatarURL(void 0, 512, true)}
@ -85,36 +85,42 @@ function ContributorModal({ user, modalProps }: { user: User; modalProps: Render
</Paragraph>
)
}
actionBarInput={
hasLinks && (
<div
className={cl("links")}
style={{ width: "100%", justifyContent: "flex-end" }}
>
{website && (
<WebsiteButton
text={website}
href={`https://${website}`}
/>
)}
{githubName && (
<GithubButton
text={githubName}
href={`https://github.com/${githubName}`}
/>
)}
</div>
)
}
>
{!!plugins.length && (
<div className={cl("plugins")}>
{plugins.map(p =>
<PluginCard
key={p.name}
plugin={p}
disabled={p.required ?? false}
onRestartNeeded={() => showToast("Restart to apply changes!")}
/>
)}
</div>
)}
{hasLinks && (
<div className={classes("vc-settings-modal-links", cl("links"))}>
{website && (
<WebsiteButton
text={website}
href={`https://${website}`}
/>
)}
{githubName && (
<GithubButton
text={githubName}
href={`https://github.com/${githubName}`}
/>
)}
</div>
)}
<div className={cl("root")}>
{!!plugins.length && (
<div className={cl("plugins")}>
{plugins.map(p =>
<PluginCard
key={p.name}
plugin={p}
disabled={p.required ?? false}
onRestartNeeded={() => showToast("Restart to apply changes!")}
/>
)}
</div>
)}
</div>
</Modal>
);
}

View file

@ -17,16 +17,13 @@
*/
import { definePluginSettings } from "@api/Settings";
import { BaseText } from "@components/BaseText";
import { CodeBlock } from "@components/CodeBlock";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Heading } from "@components/Heading";
import { EquicordDevs } from "@utils/constants";
import { copyWithToast } from "@utils/discord";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { Button, ChannelStore } from "@webpack/common";
import { ChannelStore, Modal, openModal } from "@webpack/common";
function DecodeIcon() {
return (
@ -64,32 +61,26 @@ function decodeBase64Strings(base64Strings) {
}
function openDecodedBase64Modal(decodedContent) {
const key = openModal(props => (
openModal(props => (
<ErrorBoundary>
<ModalRoot {...props} size={ModalSize.LARGE}>
<ModalHeader>
<BaseText size="lg" weight="semibold" style={{ flexGrow: 1 }}>Decoded Base64 Content</BaseText>
<ModalCloseButton onClick={() => closeModal(key)} />
</ModalHeader>
<ModalContent>
<div style={{ padding: "16px 0" }}>
<Heading>Decoded Content</Heading>
{decodedContent.map((content, index) => (
<CodeBlock key={index} content={content} lang="" />
))}
</div>
</ModalContent >
<ModalFooter>
<Flex gap={10}>
{decodedContent.map((content, index) => (
<Button key={index} onClick={() => copyWithToast(content, "Decoded content copied to clipboard!")}>
Copy Decoded Content {index + 1}
</Button>
))}
</Flex>
</ModalFooter>
</ModalRoot >
</ErrorBoundary >
<Modal
{...props}
size="lg"
title="Decoded Base64 Content"
actions={decodedContent.map((content, index) => ({
text: `Copy Decoded Content ${index + 1}`,
variant: "primary",
onClick: () => copyWithToast(content, "Decoded content copied to clipboard!")
}))}
>
<div style={{ padding: "16px 0" }}>
<Heading>Decoded Content</Heading>
{decodedContent.map((content, index) => (
<CodeBlock key={index} content={content} lang="" />
))}
</div>
</Modal>
</ErrorBoundary>
));
}

View file

@ -10,9 +10,8 @@ import { CircleQuestionIcon, DiscoveryIcon, EnvelopeIcon, FriendsIcon, NitroIcon
import { classNameFactory } from "@utils/css";
import { getGuildAcronym, getIntlMessage } from "@utils/discord";
import { classes } from "@utils/misc";
import { closeModal, openModal } from "@utils/modal";
import { findComponentByCodeLazy } from "@webpack";
import { Avatar, ChannelStore, ContextMenuApi, FluxDispatcher, GuildStore, Menu, React, ReadStateStore, ReadStateUtils, SelectedChannelStore, SelectedGuildStore, TextInput, Tooltip, useDrag, useDrop, useEffect, useRef, UserStore, useState } from "@webpack/common";
import { Avatar, ChannelStore, closeModal, ContextMenuApi, FluxDispatcher, GuildStore, Menu, openModal,React, ReadStateStore, ReadStateUtils, SelectedChannelStore, SelectedGuildStore, TextInput, Tooltip, useDrag, useDrop, useEffect, useRef, UserStore, useState } from "@webpack/common";
import { NotificationDot } from "./ChannelTab";
import { BookmarkContextMenu, EditModal } from "./ContextMenus";

View file

@ -11,8 +11,8 @@ import { bookmarkFolderColors, bookmarkPlaceholderName, closeOtherTabs, closeTab
import { Bookmark, BookmarkFolder, Bookmarks, ChannelTabsProps, UseBookmarkMethods } from "@equicordplugins/channelTabs/util/types";
import { getIntlMessage } from "@utils/discord";
import { Margins } from "@utils/margins";
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import { Button, ChannelStore, ColorPicker, FluxDispatcher, Menu, ReadStateStore, ReadStateUtils, Select, TextInput, useMemo, useState } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Button, ChannelStore, closeModal, ColorPicker, FluxDispatcher, Menu, Modal, openModal, ReadStateStore, ReadStateUtils, Select, TextInput, useMemo, useState } from "@webpack/common";
const legacyFolderColors: Record<string, string> = {
"var(--channeltabs-red)": bookmarkFolderColors.Red,
@ -115,7 +115,7 @@ function FolderChipPreview({ name, color, iconName }: { name: string; color: str
}
function FolderIconPickerModal({ modalProps, modalKey, name, color, iconName, onSelect, onColorChange }: {
modalProps: ModalProps,
modalProps: RenderModalProps,
modalKey: string,
name: string,
color: string,
@ -133,73 +133,72 @@ function FolderIconPickerModal({ modalProps, modalKey, name, color, iconName, on
}, [iconNames, search]);
return (
<ModalRoot {...modalProps}>
<ModalHeader>
<BaseText size="lg" weight="semibold">Choose Folder Icon</BaseText>
</ModalHeader>
<ModalContent>
<Heading className={Margins.top16}>Preview</Heading>
<FolderChipPreview name={name} color={localColor} iconName={iconName} />
<Heading className={Margins.top16}>Icon Color</Heading>
<div className={Margins.top8}>
<ColorPicker
color={colorToInt(localColor)}
onChange={value => {
const nextColor = intToColor(value);
setLocalColor(nextColor);
onColorChange(nextColor);
}}
showEyeDropper={false}
/>
</div>
<Heading className={Margins.top16}>Search</Heading>
<TextInput
value={search}
placeholder={`Search ${iconNames.length} icons...`}
onChange={setSearch}
<Modal
{...modalProps}
size="sm"
title={<BaseText size="lg" weight="semibold">Choose Folder Icon</BaseText>}
actions={[
{
text: "Close",
variant: "secondary",
onClick: () => closeModal(modalKey)
}
]}
>
<Heading className={Margins.top16}>Preview</Heading>
<FolderChipPreview name={name} color={localColor} iconName={iconName} />
<Heading className={Margins.top16}>Icon Color</Heading>
<div className={Margins.top8}>
<ColorPicker
color={colorToInt(localColor)}
onChange={value => {
const nextColor = intToColor(value);
setLocalColor(nextColor);
onColorChange(nextColor);
}}
showEyeDropper={false}
/>
<div style={{
display: "grid",
gap: 8,
gridTemplateColumns: "repeat(auto-fill, minmax(52px, 1fr))",
marginTop: 16,
maxHeight: 320,
overflowY: "auto"
}}>
{filteredIconNames.map(name => (
<button
key={name}
onClick={() => {
onSelect(name);
closeModal(modalKey);
}}
style={{
alignItems: "center",
background: name === iconName ? "var(--background-modifier-hover)" : "var(--background-secondary)",
border: "1px solid var(--border-subtle)",
borderRadius: 8,
cursor: "pointer",
display: "flex",
height: 52,
justifyContent: "center",
padding: 0
}}
title={name}
type="button"
>
<FolderGlyph color={localColor} iconName={name} />
</button>
))}
</div>
</ModalContent>
<ModalFooter>
<Button
color={Button.Colors.TRANSPARENT}
look={Button.Looks.FILLED}
onClick={() => closeModal(modalKey)}
>Close</Button>
</ModalFooter>
</ModalRoot>
</div>
<Heading className={Margins.top16}>Search</Heading>
<TextInput
value={search}
placeholder={`Search ${iconNames.length} icons...`}
onChange={setSearch}
/>
<div style={{
display: "grid",
gap: 8,
gridTemplateColumns: "repeat(auto-fill, minmax(52px, 1fr))",
marginTop: 16,
maxHeight: 320,
overflowY: "auto"
}}>
{filteredIconNames.map(name => (
<button
key={name}
onClick={() => {
onSelect(name);
closeModal(modalKey);
}}
style={{
alignItems: "center",
background: name === iconName ? "var(--background-modifier-hover)" : "var(--background-secondary)",
border: "1px solid var(--border-subtle)",
borderRadius: 8,
cursor: "pointer",
display: "flex",
height: 52,
justifyContent: "center",
padding: 0
}}
title={name}
type="button"
>
<FolderGlyph color={localColor} iconName={name} />
</button>
))}
</div>
</Modal>
);
}
@ -263,7 +262,7 @@ function FolderAppearanceFields({
}
export function EditModal({ modalProps, modalKey, bookmark, onSave }: {
modalProps: ModalProps,
modalProps: RenderModalProps,
modalKey: string,
bookmark: Bookmark | BookmarkFolder,
onSave: (name: string, color: string, iconName?: string) => void;
@ -274,46 +273,47 @@ export function EditModal({ modalProps, modalKey, bookmark, onSave }: {
const placeholder = bookmarkPlaceholderName(bookmark);
return (
<ModalRoot {...modalProps}>
<ModalHeader>
<BaseText size="lg" weight="semibold">Edit Bookmark</BaseText>
</ModalHeader>
<ModalContent>
{isBookmarkFolder(bookmark)
? <FolderAppearanceFields
name={name}
setName={setName}
color={color}
setColor={setColor}
iconName={iconName}
setIconName={setIconName}
<Modal
{...modalProps}
size="sm"
title={<BaseText size="lg" weight="semibold">Edit Bookmark</BaseText>}
actions={[
{
text: "Save",
variant: "primary",
onClick: () => onSave(name || placeholder, color, iconName)
},
{
text: "Cancel",
variant: "secondary",
onClick: () => closeModal(modalKey)
}
]}
>
{isBookmarkFolder(bookmark)
? <FolderAppearanceFields
name={name}
setName={setName}
color={color}
setColor={setColor}
iconName={iconName}
setIconName={setIconName}
placeholder={placeholder}
/>
: <>
<Heading className={Margins.top16}>Bookmark Name</Heading>
<TextInput
value={name === placeholder ? undefined : name}
placeholder={placeholder}
onChange={setName}
/>
: <>
<Heading className={Margins.top16}>Bookmark Name</Heading>
<TextInput
value={name === placeholder ? undefined : name}
placeholder={placeholder}
onChange={setName}
/>
</>}
</ModalContent>
<ModalFooter>
<Button
onClick={() => onSave(name || placeholder, color, iconName)}
>Save</Button>
<Button
color={Button.Colors.TRANSPARENT}
look={Button.Looks.FILLED}
onClick={() => closeModal(modalKey)}
>Cancel</Button>
</ModalFooter>
</ModalRoot>
</>}
</Modal>
);
}
function AddToFolderModal({ modalProps, modalKey, bookmarks, onSave }: {
modalProps: any,
modalProps: RenderModalProps,
modalKey: string,
bookmarks: Bookmarks,
onSave: (folderIndex: number, folderName: string, folderColor: string, iconName?: string) => void;
@ -324,78 +324,79 @@ function AddToFolderModal({ modalProps, modalKey, bookmarks, onSave }: {
const [iconName, setIconName] = useState<string | undefined>();
return (
<ModalRoot {...modalProps}>
<ModalHeader>
<BaseText size="lg" weight="semibold">Add Bookmark to Folder</BaseText>
</ModalHeader>
<ModalContent>
<Heading className={Margins.top16}>Select a folder</Heading>
<Select
options={[...Object.entries(bookmarks)
.filter(([, bookmark]) => isBookmarkFolder(bookmark))
.map(([index, bookmark]) => ({
label: bookmark.name,
value: parseInt(index, 10)
})),
{
label: "Create one",
value: -1,
default: true
}]}
isSelected={v => v === folderIndex}
select={setIndex}
serialize={String}
/>
{folderIndex === -1 && <FolderAppearanceFields
name={folderName}
setName={setFolderName}
color={folderColor}
setColor={setFolderColor}
iconName={iconName}
setIconName={setIconName}
/>}
</ModalContent>
<ModalFooter>
<Button
onClick={() => onSave(folderIndex, folderName, folderColor, iconName)}
>Save</Button>
<Button
color={Button.Colors.TRANSPARENT}
look={Button.Looks.FILLED}
onClick={() => closeModal(modalKey)}
>Cancel</Button>
</ModalFooter>
</ModalRoot>
<Modal
{...modalProps}
size="sm"
title={<BaseText size="lg" weight="semibold">Add Bookmark to Folder</BaseText>}
actions={[
{
text: "Save",
variant: "primary",
onClick: () => onSave(folderIndex, folderName, folderColor, iconName)
},
{
text: "Cancel",
variant: "secondary",
onClick: () => closeModal(modalKey)
}
]}
>
<Heading className={Margins.top16}>Select a folder</Heading>
<Select
options={[...Object.entries(bookmarks)
.filter(([, bookmark]) => isBookmarkFolder(bookmark))
.map(([index, bookmark]) => ({
label: bookmark.name,
value: parseInt(index, 10)
})),
{
label: "Create one",
value: -1,
default: true
}]}
isSelected={v => v === folderIndex}
select={setIndex}
serialize={String}
/>
{folderIndex === -1 && <FolderAppearanceFields
name={folderName}
setName={setFolderName}
color={folderColor}
setColor={setFolderColor}
iconName={iconName}
setIconName={setIconName}
/>}
</Modal>
);
}
function DeleteFolderConfirmationModal({ modalProps, modalKey, onConfirm }) {
function DeleteFolderConfirmationModal({ modalProps, modalKey, onConfirm }: {
modalProps: RenderModalProps,
modalKey: string,
onConfirm: () => void;
}) {
return (
<ModalRoot {...modalProps}>
<ModalHeader>
<BaseText size="lg" weight="semibold">Are you sure?</BaseText>
</ModalHeader>
<ModalContent>
<Paragraph className={Margins.top16}>
Deleting a bookmark folder will also delete all bookmarks within it.
</Paragraph>
</ModalContent>
<ModalFooter>
<Button
color={Button.Colors.RED}
onClick={onConfirm}
>
Delete
</Button>
<Button
color={Button.Colors.TRANSPARENT}
look={Button.Looks.FILLED}
onClick={() => closeModal(modalKey)}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>
<Modal
{...modalProps}
size="sm"
title={<BaseText size="lg" weight="semibold">Are you sure?</BaseText>}
actions={[
{
text: "Delete",
variant: "critical-primary",
onClick: onConfirm
},
{
text: "Cancel",
variant: "secondary",
onClick: () => closeModal(modalKey)
}
]}
>
<Paragraph className={Margins.top16}>
Deleting a bookmark folder will also delete all bookmarks within it.
</Paragraph>
</Modal>
);
}

View file

@ -7,8 +7,8 @@
import { CogWheel } from "@components/Icons";
import { classNameFactory } from "@utils/css";
import { classes } from "@utils/misc";
import { type ModalProps,ModalRoot, ModalSize, openModal } from "@utils/modal";
import { TextInput, useCallback, useEffect, useMemo, useRef, useState } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Modal,openModal, TextInput, useCallback, useEffect, useMemo, useRef, useState } from "@webpack/common";
import {
type CommandEntry, getCategoryGroupLabel, getCategoryWeight, getCommandSearchText, getRecentRank, getRegistryVersion, listCommands, subscribeRegistry
@ -56,7 +56,7 @@ export function openCommandPicker(props: CommandPickerProps) {
));
}
function CommandPickerModal({ modalProps, commands: providedCommands, allowMultiple = false, initialQuery = "", initialSelectedIds = [], includeHiddenInSearch = false, emptyStateText = "No commands found", filter: filterPredicate, onSelect, onComplete, onClose }: CommandPickerProps & { modalProps: ModalProps; }) {
function CommandPickerModal({ modalProps, commands: providedCommands, allowMultiple = false, initialQuery = "", initialSelectedIds = [], includeHiddenInSearch = false, emptyStateText = "No commands found", filter: filterPredicate, onSelect, onComplete, onClose }: CommandPickerProps & { modalProps: RenderModalProps; }) {
const [query, setQuery] = useState(initialQuery);
const [registryVersion, setRegistryVersion] = useState(() => getRegistryVersion());
const [selectedIndex, setSelectedIndex] = useState(-1);
@ -321,8 +321,8 @@ function CommandPickerModal({ modalProps, commands: providedCommands, allowMulti
};
return (
<ModalRoot {...modalProps} size={ModalSize.SMALL} className={classes(cn("vc-command-palette"), cl("picker"))}>
<div className={classes(cl("shell"), cl("picker-shell"))} onKeyDown={handleKeyDown}>
<Modal {...modalProps} size="sm" title="Command Picker">
<div className={classes(cl("shell"), cl("picker-shell"), cn("vc-command-palette"), cl("picker"))} onKeyDown={handleKeyDown}>
<div className={cl("input")}>
<TextInput
autoFocus
@ -373,6 +373,6 @@ function CommandPickerModal({ modalProps, commands: providedCommands, allowMulti
})}
</div>
</div>
</ModalRoot>
</Modal>
);
}

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { closeModal, openModal } from "@utils/modal";
import { closeModal, openModal } from "@webpack/common";
import { CommandPaletteModal } from "./ui/CommandPaletteModal";

View file

@ -11,8 +11,8 @@ import { classNameFactory, classNameToSelector } from "@utils/css";
import { copyWithToast } from "@utils/discord";
import { Logger } from "@utils/Logger";
import { classes } from "@utils/misc";
import { type ModalProps, ModalRoot, ModalSize } from "@utils/modal";
import { TextInput, Toasts, useEffect, useMemo, useRef, useState } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Modal,TextInput, Toasts, useEffect, useMemo, useRef, useState } from "@webpack/common";
import type { KeyboardEvent as ReactKeyboardEvent } from "react";
import { buildQueryResolution } from "../actions/executors";
@ -282,7 +282,7 @@ function createInitialPageState(ref: PalettePageRef): PalettePageValuesState {
};
}
export function CommandPaletteModal({ modalProps, instanceKey }: { modalProps: ModalProps; instanceKey: number; }) {
export function CommandPaletteModal({ modalProps, instanceKey }: { modalProps: RenderModalProps; instanceKey: number; }) {
const {
compactStartEnabled = true,
closeAfterExecute = true
@ -1469,12 +1469,12 @@ export function CommandPaletteModal({ modalProps, instanceKey }: { modalProps: M
}, [activePage, activePageFieldKey, activePageSpec, activePageState, activePageSuggestions]);
return (
<ModalRoot
<Modal
{...modalProps}
className={cn("vc-command-palette", compact && cl("compact"))}
size={compact ? ModalSize.SMALL : ModalSize.LARGE}
size={compact ? "sm" : "lg"}
title="Command Palette"
>
<div className={cl("shell")} onKeyDown={onKeyDown}>
<div className={cn("vc-command-palette", cl("shell"), compact && cl("compact"))} onKeyDown={onKeyDown}>
{!isPageOpen && (
<CommandPaletteInput
inputRef={mainInputRef}
@ -1656,6 +1656,6 @@ export function CommandPaletteModal({ modalProps, instanceKey }: { modalProps: M
/>
)}
</div>
</ModalRoot>
</Modal>
);
}

View file

@ -4,8 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { closeModal, ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
import { Button, Menu, Slider, TextInput, useState } from "@webpack/common";
import { Button, closeModal, Menu, Modal,openModalLazy, Slider, TextInput, useState } from "@webpack/common";
import { folderIconsData, settings } from "./settings";
import { folderProp, int2rgba, setFolderData } from "./util";
@ -95,17 +94,12 @@ export function makeContextItem(a: folderProp) {
action={() => {
openModalLazy(async () => {
return props => (
<ModalRoot {...props}>
<ModalHeader >
<div style={{
color: "white"
}}>
Set a New Icon.
</div>
</ModalHeader>
<ModalContent>
<ImageModal folderId={a.folderId} folderColor={a.folderColor} />
</ModalContent>
<Modal
{...props}
size="sm"
title="Set a New Icon."
>
<ImageModal folderId={a.folderId} folderColor={a.folderColor} />
<div style={{
color: "white",
margin: "2.5%",
@ -113,7 +107,7 @@ export function makeContextItem(a: folderProp) {
}}>
You might have to hover the folder after setting in order for it to refresh.
</div>
</ModalRoot>
</Modal>
);
},
{

View file

@ -5,17 +5,17 @@
*/
import { set } from "@api/DataStore";
import { HeadingPrimary, HeadingSecondary } from "@components/Heading";
import { HeadingSecondary } from "@components/Heading";
import { classNameFactory } from "@utils/css";
import { Margins } from "@utils/margins";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { Button, ColorPicker, useState } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { ColorPicker, Modal, React, useState } from "@webpack/common";
import { colors, DATASTORE_KEY } from "./index";
const cl = classNameFactory("vc-customColors-");
export function SetColorModal({ id, modalProps }: { id: string, modalProps: ModalProps; }) {
export function SetColorModal({ id, modalProps }: { id: string, modalProps: RenderModalProps; }) {
const initialColor = parseInt(colors[id], 16) || 372735;
// color picker default to current color set for user (if null it's 0x05afff :3 )
@ -26,7 +26,7 @@ export function SetColorModal({ id, modalProps }: { id: string, modalProps: Moda
setColorPickerColor(color);
}
function handleKey(e: KeyboardEvent) {
function handleKey(e: React.KeyboardEvent) {
if (e.key === "Enter")
saveUserColor();
}
@ -44,14 +44,24 @@ export function SetColorModal({ id, modalProps }: { id: string, modalProps: Moda
}
return (
<ModalRoot {...modalProps}>
<ModalHeader className={cl("modal-header")}>
<HeadingPrimary>
Custom Color
</HeadingPrimary>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent className={cl("modal-content")} onKeyDown={handleKey}>
<Modal
{...modalProps}
size="sm"
title="Custom Color"
actions={[
{
text: "Save",
variant: "primary",
onClick: saveUserColor
},
{
text: "Delete Entry",
variant: "dangerPrimary",
onClick: deleteUserColor
}
]}
>
<div onKeyDown={handleKey} className={cl("modal-content")}>
<section className={Margins.bottom16}>
<HeadingSecondary>
Pick a Color
@ -62,22 +72,7 @@ export function SetColorModal({ id, modalProps }: { id: string, modalProps: Moda
showEyeDropper={false}
/>
</section>
</ModalContent>
<ModalFooter className={cl("modal-footer")}>
<Button
color={Button.Colors.RED}
onClick={deleteUserColor}
>
Delete Entry
</Button>
<Button
color={Button.Colors.BRAND}
onClick={saveUserColor}
>
Save
</Button>
</ModalFooter>
</ModalRoot>
</div>
</Modal>
);
}

View file

@ -10,11 +10,10 @@ import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { get } from "@api/DataStore";
import { definePluginSettings, Settings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { Channel, User } from "@vencord/discord-types";
import { extractAndLoadChunksLazy } from "@webpack";
import { ChannelStore, Menu, SelectedChannelStore } from "@webpack/common";
import { ChannelStore, Menu, openModal,SelectedChannelStore } from "@webpack/common";
import { SetColorModal } from "./SetColorModal";

View file

@ -8,10 +8,11 @@ import "./styles.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { Paginator, requirePaginator } from "@plugins/reviewDB/components/ReviewModal";
import { ModalCloseButton } from "@utils/modal";
import { Message } from "@vencord/discord-types";
import { findCssClassesLazy } from "@webpack";
import { findComponentByCodeLazy, findCssClassesLazy } from "@webpack";
import { React, useRef, useState } from "@webpack/common";
const CloseButton = findComponentByCodeLazy("CLOSE_BUTTON_LABEL");
import { MutableRefObject } from "react";
import { jumper } from "./index";
@ -53,7 +54,7 @@ export default function ReplyNavigator({ replies }: { replies: Message[]; }) {
totalCount={replies.length}
onPageChange={processPageChange}
/>
<ModalCloseButton className={"vc-findreply-close"} onClick={() => setVisible(false)} />
<CloseButton className={"vc-findreply-close"} onClick={() => setVisible(false)} />
</div>
</ErrorBoundary>
);

View file

@ -13,9 +13,9 @@ import { Flex } from "@components/Flex";
import { Paragraph } from "@components/Paragraph";
import { Devs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin from "@utils/types";
import { Forms, RelationshipStore } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Forms, Modal,openModal, RelationshipStore } from "@webpack/common";
interface rankInfo {
title: string;
@ -84,10 +84,12 @@ const ranks: rankInfo[] =
];
function openRankModal(rank: rankInfo) {
openModal(props => (
openModal((props: RenderModalProps) => (
<ErrorBoundary>
<ModalRoot {...props} size={ModalSize.DYNAMIC}>
<ModalHeader>
<Modal
{...props}
size="sm"
title={
<Flex className={cl("flex")}>
<Forms.FormTitle
className={cl("img")}
@ -97,15 +99,14 @@ function openRankModal(rank: rankInfo) {
{rank.title}
</Forms.FormTitle>
</Flex>
</ModalHeader>
<ModalContent>
<div className={cl("text")}>
<Paragraph>
{rank.description}
</Paragraph>
</div>
</ModalContent>
</ModalRoot>
}
>
<div className={cl("text")}>
<Paragraph>
{rank.description}
</Paragraph>
</div>
</Modal>
</ErrorBoundary >
));
}

View file

@ -5,10 +5,9 @@
*/
import { classNameFactory } from "@utils/css";
import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize } from "@utils/modal";
import { Channel } from "@vencord/discord-types";
import { Channel, RenderModalProps } from "@vencord/discord-types";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { Avatar, Button, ChannelStore, MessageStore, React, Text, UserStore } from "@webpack/common";
import { Avatar, Button, ChannelStore, MessageStore, Modal,React, Text, UserStore } from "@webpack/common";
const cl = classNameFactory("vc-boo-");
@ -37,9 +36,8 @@ function GroupDmsIcon({ channel }: { channel: Channel; }) {
}
interface GhostedUsersModalProps {
modalProps: any;
modalProps: RenderModalProps;
ghostedChannels: string[];
onClose: () => void;
onClearGhost: (channelId: string) => void;
}
@ -57,14 +55,14 @@ export function getChannelDisplayName(channelId: string): string {
return user?.username || "Unknown User";
}
export function GhostedUsersModal({ modalProps, ghostedChannels: initialChannels, onClose, onClearGhost }: GhostedUsersModalProps) {
export function GhostedUsersModal({ modalProps, ghostedChannels: initialChannels, onClearGhost }: GhostedUsersModalProps) {
const [ghostedChannels, setGhostedChannels] = React.useState(initialChannels);
const handleChannelClick = (channelId: string) => {
const channel = ChannelStore.getChannel(channelId);
if (channel) {
SelectedChannelActionCreators.selectPrivateChannel(channelId);
onClose();
modalProps.onClose();
}
};
@ -83,77 +81,67 @@ export function GhostedUsersModal({ modalProps, ghostedChannels: initialChannels
};
return (
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<ModalHeader>
<Text
variant="heading-lg/semibold"
className={cl("modal-header")}
>
Ghosted Users ({ghostedChannels.length})
</Text>
{ghostedChannels.length > 0 && (
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.PRIMARY}
onClick={handleClearAll}
className={cl("clear")}
>
Clear All
</Button>
)}
<ModalCloseButton onClick={onClose} />
</ModalHeader>
<ModalContent>
<div className={cl("modal-content")}>
{ghostedChannels.length === 0 ? (
<Text variant="text-md/normal">No ghosts here!</Text>
) : (
ghostedChannels.map(channelId => {
const channel = ChannelStore.getChannel(channelId);
if (!channel) return null;
<Modal
{...modalProps}
size="md"
title={`Ghosted Users (${ghostedChannels.length})`}
actions={ghostedChannels.length > 0 ? [
{
text: "Clear All",
variant: "primary",
onClick: handleClearAll
}
] : []}
>
<div className={cl("modal-content")}>
{ghostedChannels.length === 0 ? (
<Text variant="text-md/normal">No ghosts here!</Text>
) : (
ghostedChannels.map(channelId => {
const channel = ChannelStore.getChannel(channelId);
if (!channel) return null;
const lastMessage = MessageStore.getMessages(channelId)?.last();
const lastMessageDate = lastMessage?.timestamp ? formatMessageDate(lastMessage.timestamp) : "";
const lastMessage = MessageStore.getMessages(channelId)?.last();
const lastMessageDate = lastMessage?.timestamp ? formatMessageDate(lastMessage.timestamp) : "";
const displayName = getChannelDisplayName(channel.id);
const userId = channel?.recipients?.[0] ?? "";
return (
<div
key={channelId}
onClick={() => handleChannelClick(channelId)}
className={cl("ghosted-entry")}
>
{channel.isGroupDM() ?
<GroupDmsIcon
channel={channel}
/> : <Avatar
src={UserStore.getUser(userId)?.getAvatarURL(undefined, 128, true)}
size="SIZE_40"
aria-label={displayName}
/>}
<div className={cl("user-info")}>
<Text variant="text-md/normal">
{displayName}
const displayName = getChannelDisplayName(channel.id);
const userId = channel?.recipients?.[0] ?? "";
return (
<div
key={channelId}
onClick={() => handleChannelClick(channelId)}
className={cl("ghosted-entry")}
>
{channel.isGroupDM() ?
<GroupDmsIcon
channel={channel}
/> : <Avatar
src={UserStore.getUser(userId)?.getAvatarURL(undefined, 128, true)}
size="SIZE_40"
aria-label={displayName}
/>}
<div className={cl("user-info")}>
<Text variant="text-md/normal">
{displayName}
</Text>
{lastMessageDate && (
<Text variant="text-xs/normal" className={cl("modal-text")}>
{lastMessageDate}
</Text>
{lastMessageDate && (
<Text variant="text-xs/normal" className={cl("modal-text")}>
{lastMessageDate}
</Text>
)}
</div>
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.PRIMARY}
onClick={e => handleClearClick(e, channelId)}
>
Clear
</Button>
)}
</div>
);
})
)}
</div>
</ModalContent>
</ModalRoot>
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.PRIMARY}
onClick={e => handleClearClick(e, channelId)}
>
Clear
</Button>
</div>
);
})
)}
</div>
</Modal>
);
}

View file

@ -12,10 +12,9 @@ import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, EquicordDevs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { closeModal, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { Channel } from "@vencord/discord-types";
import { Menu, Tooltip, useEffect, useState } from "@webpack/common";
import { Menu, openModal,Tooltip, useEffect, useState } from "@webpack/common";
import { Boo, clearChannelFromGhost, getBooCount, getGhostedChannels, onBooCountChange } from "./Boo";
import { getChannelDisplayName, GhostedUsersModal } from "./GhostedUsersModal";
@ -85,12 +84,11 @@ function BooIndicator() {
const handleClick = () => {
const ghostedChannels = getGhostedChannels();
const modalKey = openModal(modalProps => (
openModal(modalProps => (
<ErrorBoundary>
<GhostedUsersModal
modalProps={modalProps}
ghostedChannels={ghostedChannels}
onClose={() => closeModal(modalKey)}
onClearGhost={clearChannelFromGhost}
/>
</ErrorBoundary>

View file

@ -8,8 +8,8 @@ import { Flex } from "@components/Flex";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { classes } from "@utils/misc";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Button, FluxDispatcher, TextInput, useCallback, useState } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Button, FluxDispatcher, Modal, openModal, TextInput, useCallback, useState } from "@webpack/common";
import { settings } from "../settings";
import { Collection, Gif } from "../types";
@ -49,124 +49,116 @@ export function openRenameCollectionModal(name: string) {
openModal(props => <RenameCollectionModal props={props} name={name} />);
}
function InfoModal({ props, title, rows }: { props: ModalProps; title: string; rows: { label: string; value: string; }[]; }) {
function InfoModal({ props, title, rows }: { props: RenderModalProps; title: string; rows: { label: string; value: string; }[]; }) {
return (
<ModalRoot {...props} size={ModalSize.SMALL} className={cl("modal")}>
<ModalHeader separator={false} className={cl("modal-header")}>
<Paragraph className={cl("modal-title")}>{title}</Paragraph>
</ModalHeader>
<ModalContent className={cl("modal-content")}>
<section>
{rows.map(row => (
<Flex key={row.label} className={cl("info-row")}>
<Heading className={cl("info-title")}>{row.label}</Heading>
<Paragraph className={cl("info-text")}>{row.value}</Paragraph>
</Flex>
<Modal
{...props}
size="sm"
title={title}
actions={[
{ text: "Close", variant: "secondary", onClick: props.onClose }
]}
>
<section>
{rows.map(row => (
<Flex key={row.label} className={cl("info-row")}>
<Heading className={cl("info-title")}>{row.label}</Heading>
<Paragraph className={cl("info-text")}>{row.value}</Paragraph>
</Flex>
))}
</section>
</Modal>
);
}
function MoveToCollectionModal({ props, gifId }: { props: RenderModalProps; gifId: string; }) {
return (
<Modal
{...props}
size="sm"
title="Move To Collection"
actions={[
{ text: "Close", variant: "secondary", onClick: props.onClose }
]}
>
<Heading style={{ marginBottom: "10px" }}>Select a collection to move the item to</Heading>
<div className={cl("buttons")}>
{cache_collections
.filter(col => col.name !== getItemCollectionNameFromId(gifId))
.map(col => (
<Button
key={col.name}
className={cl("button")}
onClick={async () => {
const fromCollection = getItemCollectionNameFromId(gifId);
if (!fromCollection) return;
await moveGifToCollection(gifId, fromCollection, col.name);
FluxDispatcher.dispatch({ type: "GIF_PICKER_QUERY", query: "" });
FluxDispatcher.dispatch({ type: "GIF_PICKER_QUERY", query: fromCollection });
props.onClose();
}}
>
{stripPrefix(col.name)}
</Button>
))}
</section>
</ModalContent>
<ModalFooter className={cl("modal-footer")}>
<Button onClick={props.onClose}>Close</Button>
</ModalFooter>
</ModalRoot>
</div>
</Modal>
);
}
function MoveToCollectionModal({ props, gifId }: { props: ModalProps; gifId: string; }) {
return (
<ModalRoot {...props} size={ModalSize.SMALL} className={cl("modal")}>
<ModalHeader separator={false} className={cl("modal-header")}>
<Paragraph className={cl("modal-title")}>Move To Collection</Paragraph>
</ModalHeader>
<ModalContent className={cl("modal-content")}>
<Heading>Select a collection to move the item to</Heading>
<div className={cl("buttons")}>
{cache_collections
.filter(col => col.name !== getItemCollectionNameFromId(gifId))
.map(col => (
<Button
key={col.name}
className={cl("button")}
onClick={async () => {
const fromCollection = getItemCollectionNameFromId(gifId);
if (!fromCollection) return;
await moveGifToCollection(gifId, fromCollection, col.name);
FluxDispatcher.dispatch({ type: "GIF_PICKER_QUERY", query: "" });
FluxDispatcher.dispatch({ type: "GIF_PICKER_QUERY", query: fromCollection });
props.onClose();
}}
>
{stripPrefix(col.name)}
</Button>
))}
</div>
</ModalContent>
<ModalFooter className={cl("modal-footer")}>
<Button onClick={props.onClose}>Close</Button>
</ModalFooter>
</ModalRoot>
);
}
function CreateCollectionModal({ props, gif }: { props: ModalProps; gif: Gif; }) {
function CreateCollectionModal({ props, gif }: { props: RenderModalProps; gif: Gif; }) {
const [name, setName] = useState("");
const onSubmit = useCallback((e: React.FormEvent) => {
e.preventDefault();
const onSubmit = useCallback((e?: React.FormEvent) => {
e?.preventDefault();
if (!name.length) return;
createCollection(name, [gif]);
props.onClose();
}, [name, gif, props]);
return (
<ModalRoot {...props}>
<Modal
{...props}
size="sm"
title="Create Collection"
actions={[
{ text: "Create", onClick: onSubmit, disabled: !name.length, variant: "primary" }
]}
>
<form onSubmit={onSubmit}>
<ModalHeader>
<Paragraph>Create Collection</Paragraph>
</ModalHeader>
<ModalContent>
<Heading className={cl("rename-text")}>Collection Name</Heading>
<TextInput onChange={setName} />
</ModalContent>
<ModalFooter>
<Button type="submit" color={Button.Colors.GREEN} disabled={!name.length} onClick={onSubmit}>
Create
</Button>
</ModalFooter>
<Heading className={cl("rename-text")}>Collection Name</Heading>
<TextInput onChange={setName} />
</form>
</ModalRoot>
</Modal>
);
}
function RenameCollectionModal({ props, name }: { props: ModalProps; name: string; }) {
function RenameCollectionModal({ props, name }: { props: RenderModalProps; name: string; }) {
const prefix = settings.store.collectionPrefix;
const strippedName = name.startsWith(prefix) ? name.slice(prefix.length) : name;
const [newName, setNewName] = useState(strippedName);
const tooLong = newName.length >= 25;
const onSubmit = useCallback(async (e: React.FormEvent) => {
e.preventDefault();
const onSubmit = useCallback(async (e?: React.FormEvent) => {
e?.preventDefault();
if (!newName.length || tooLong) return;
await renameCollection(name, newName);
props.onClose();
}, [newName, name, tooLong, props]);
return (
<ModalRoot {...props}>
<Modal
{...props}
size="sm"
title="Rename Collection"
actions={[
{ text: "Rename", onClick: onSubmit, disabled: !newName.length || tooLong, variant: "primary" }
]}
>
<form onSubmit={onSubmit}>
<ModalHeader>
<Paragraph>Rename Collection</Paragraph>
</ModalHeader>
<ModalContent>
<Paragraph className={cl("rename-text")}>New Collection Name</Paragraph>
<TextInput value={newName} className={classes(cl("rename-input"), tooLong ? cl("input-warning") : "")} onChange={setNewName} />
{tooLong && <Paragraph className={cl("warning-text")}>Name can't be longer than 24 characters</Paragraph>}
</ModalContent>
<ModalFooter>
<Button type="submit" color={Button.Colors.GREEN} disabled={!newName.length || tooLong} onClick={onSubmit}>
Rename
</Button>
</ModalFooter>
<Paragraph className={cl("rename-text")}>New Collection Name</Paragraph>
<TextInput value={newName} className={classes(cl("rename-input"), tooLong ? cl("input-warning") : "")} onChange={setNewName} />
{tooLong && <Paragraph className={cl("warning-text")}>Name can't be longer than 24 characters</Paragraph>}
</form>
</ModalRoot>
</Modal>
);
}

View file

@ -9,9 +9,8 @@ import { Span } from "@components/Span";
import { fetchReposByUserId, fetchReposByUsername, fetchUserInfo, GitHubUserInfo } from "@equicordplugins/githubRepos/githubApi";
import { GitHubRepo } from "@equicordplugins/githubRepos/types";
import { classes } from "@utils/misc";
import { openModal } from "@utils/modal";
import { findCssClassesLazy } from "@webpack";
import { Clickable, React, useEffect, UserProfileStore, useState } from "@webpack/common";
import { Clickable, openModal,React, useEffect, UserProfileStore, useState } from "@webpack/common";
import { ReposModal } from "./ReposModal";

View file

@ -7,8 +7,7 @@
import { BaseText } from "@components/BaseText";
import { fetchReposByUserId, fetchReposByUsername, fetchUserInfo, GitHubUserInfo } from "@equicordplugins/githubRepos/githubApi";
import { GitHubRepo } from "@equicordplugins/githubRepos/types";
import { openModal } from "@utils/modal";
import { React, useEffect, UserProfileStore, useState } from "@webpack/common";
import { openModal,React, useEffect, UserProfileStore, useState } from "@webpack/common";
import { cl, settings } from "..";
import { RepoCard } from "./RepoCard";

View file

@ -4,12 +4,10 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Button } from "@components/Button";
import { HeadingPrimary } from "@components/Heading";
import { getLanguageColor } from "@equicordplugins/githubRepos/colors";
import { GitHubRepo } from "@equicordplugins/githubRepos/types";
import { ModalContent, ModalFooter, ModalHeader, ModalRoot } from "@utils/modal";
import { React } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Modal, React } from "@webpack/common";
import { cl } from "..";
import { Star } from "./Star";
@ -20,7 +18,7 @@ interface ReposModalProps {
rootProps: any;
}
export function ReposModal({ repos, username, rootProps }: ReposModalProps) {
export function ReposModal({ repos, username, rootProps }: ReposModalProps & { rootProps: RenderModalProps; }) {
const renderTableHeader = () => (
<thead>
<tr>
@ -63,44 +61,38 @@ export function ReposModal({ repos, username, rootProps }: ReposModalProps) {
);
return (
<ModalRoot className={cl("modal")} size="large" {...rootProps}>
<ModalHeader>
<HeadingPrimary className={cl("modal-title")}>
{username}'s GitHub Repositories
</HeadingPrimary>
</ModalHeader>
<ModalContent className={cl("modal-content")}>
<div className={cl("table-container")}>
<table className={cl("table")}>
<colgroup>
<col className={cl("header-repo")} />
<col className={cl("header-description")} />
<col className={cl("header-language")} />
<col className={cl("header-stars")} />
</colgroup>
{renderTableHeader()}
<tbody>
{repos.map(renderTableRow)}
</tbody>
</table>
</div>
</ModalContent>
<ModalFooter className={cl("modal-footer")}>
<Button
className={cl("modal-footer-github")}
variant="link"
onClick={() => window.open(`https://github.com/${username}?tab=repositories`, "_blank")}
>
View on GitHub
</Button>
<Button
className={cl("modal-footer-close")}
variant="secondary"
onClick={rootProps.onClose}
>
Close
</Button>
</ModalFooter>
</ModalRoot>
<Modal
{...rootProps}
className={cl("modal")}
size="lg"
title={`${username}'s GitHub Repositories`}
actions={[
{
text: "View on GitHub",
variant: "link",
onClick: () => window.open(`https://github.com/${username}?tab=repositories`, "_blank")
},
{
text: "Close",
variant: "secondary",
onClick: rootProps.onClose
}
]}
>
<div className={cl("table-container")}>
<table className={cl("table")}>
<colgroup>
<col className={cl("header-repo")} />
<col className={cl("header-description")} />
<col className={cl("header-language")} />
<col className={cl("header-stars")} />
</colgroup>
{renderTableHeader()}
<tbody>
{repos.map(renderTableRow)}
</tbody>
</table>
</div>
</Modal>
);
}

View file

@ -4,14 +4,12 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Button } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary";
import { HeadingTertiary } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { openInviteModal } from "@utils/discord";
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
import { User } from "@vencord/discord-types";
import { React, Tooltip } from "@webpack/common";
import { RenderModalProps, User } from "@vencord/discord-types";
import { Modal, openModal, React, Tooltip } from "@webpack/common";
import { cl, GlobalBadges, INVITE_LINK } from "./utils";
@ -37,20 +35,32 @@ export function openBadgeModal(user: User) {
<BadgeModalComponent key={badge.key} tooltip={badge.tooltip} badge={badge.badge} />
));
openModal(modalprops => (
<ModalRoot {...modalprops}>
<ErrorBoundary>
<ModalHeader>
openModal((modalprops: RenderModalProps) => (
<Modal
{...modalprops}
size="sm"
title={
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<img
className={cl("modal-avatar")}
src={user.getAvatarURL(undefined, 512, true)}
alt=""
/>
<HeadingTertiary className={cl("modal-name")}>
<HeadingTertiary className={cl("modal-name")} style={{ color: "white" }}>
{user.username}
</HeadingTertiary>
</ModalHeader>
<ModalContent className={cl("modal-description")}>
</div>
}
actions={[
{
text: "Join GlobalBadges Server",
variant: "link",
onClick: () => openInviteModal(INVITE_LINK)
}
]}
>
<ErrorBoundary>
<div className={cl("modal-description")}>
{badgeDataElements.length ? (
<>
<Paragraph className={cl("modal-text")}>
@ -66,16 +76,8 @@ export function openBadgeModal(user: User) {
No global badges.
</Paragraph>
)}
</ModalContent>
<ModalFooter>
<Button
variant="link"
onClick={() => openInviteModal(INVITE_LINK)}
>
Join GlobalBadges Server
</Button>
</ModalFooter>
</div>
</ErrorBoundary>
</ModalRoot>
</Modal>
));
}

View file

@ -10,40 +10,21 @@ import { HiddenServersStore } from "@equicordplugins/hideServers/HiddenServersSt
import { classNameFactory } from "@utils/css";
import { getGuildAcronym } from "@utils/discord";
import { classes } from "@utils/misc";
import {
closeModal,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalProps,
ModalRoot,
ModalSize,
openModal,
} from "@utils/modal";
import { Guild } from "@vencord/discord-types";
import { Guild, RenderModalProps } from "@vencord/discord-types";
import { findCssClassesLazy } from "@webpack";
import { Button, IconUtils, useStateFromStores } from "@webpack/common";
import { Button, closeModal, IconUtils, Modal,openModal, useStateFromStores } from "@webpack/common";
import { SortedGuildStore } from "..";
const cl = classNameFactory("vc-hideservers-");
const IconClasses = findCssClassesLazy("icon", "acronym", "childWrapper");
function HiddenServersModal({ modalProps, close }: { modalProps: ModalProps; close(): void; }) {
function HiddenServersModal({ modalProps, close }: { modalProps: RenderModalProps; close(): void; }) {
const guilds = useStateFromStores([HiddenServersStore], () => HiddenServersStore.hiddenGuildsDetail());
return (
<ModalRoot {...modalProps} size={ModalSize.LARGE}>
<ModalHeader>
<BaseText size="lg" weight="semibold" style={{ flexGrow: 1 }}>
Hidden Servers
</BaseText>
<ModalCloseButton onClick={close} />
</ModalHeader>
<ModalContent>
<HiddenServersMenu guilds={guilds} />
</ModalContent>
</ModalRoot>
<Modal {...modalProps} size="lg" title="Hidden Servers">
<HiddenServersMenu guilds={guilds} />
</Modal>
);
}

View file

@ -5,7 +5,6 @@
*/
import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import { CodeBlock } from "@components/CodeBlock";
import { Flex } from "@components/Flex";
import { HeadingSecondary } from "@components/Heading";
@ -13,18 +12,8 @@ import { Paragraph } from "@components/Paragraph";
import { TooltipContainer } from "@components/TooltipContainer";
import { copyWithToast, getIntlMessage } from "@utils/discord";
import { Logger } from "@utils/Logger";
import {
CloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalProps,
ModalRoot,
ModalSize,
openModal
} from "@utils/modal";
import { saveFile } from "@utils/web";
import { Icon } from "@vencord/discord-types";
import { Icon, RenderModalProps } from "@vencord/discord-types";
import { findComponentByCodeLazy } from "@webpack";
import {
Clickable,
@ -32,6 +21,8 @@ import {
createRoot,
FluxDispatcher,
Menu,
Modal,
openModal,
ReactDOM,
useCallback,
useEffect,
@ -226,7 +217,7 @@ function OtherContextMenu({ iconName, Icon, color }: { iconName: string; Icon: I
);
}
function IconModal({ iconName, Icon, onClose, transitionState }: { iconName: string; Icon: Icon; } & ModalProps) {
function IconModal({ iconName, Icon, onClose, transitionState }: { iconName: string; Icon: Icon; } & RenderModalProps) {
const [color, setColor] = useColorNavigation(209);
const colorData = cssColors[color];
const colorKeys = useMemo(() => getCssColorKeys(), []);
@ -247,62 +238,59 @@ function IconModal({ iconName, Icon, onClose, transitionState }: { iconName: str
});
}, [colorKeys.length, setColor]);
const openOtherMenu = (e: React.MouseEvent) => {
ContextMenuApi.openContextMenu(e, () => (
const openOtherMenu = (e?: React.MouseEvent) => {
if (e) ContextMenuApi.openContextMenu(e, () => (
<OtherContextMenu iconName={iconName} Icon={Icon} color={color} />
));
};
return (
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM}>
<ModalHeader separator={false} className="vc-ic-modal-header">
<div className="vc-ic-modal-header-content">
<BaseText size="lg" weight="semibold" className="vc-ic-modal-title">{iconName}</BaseText>
<Modal
transitionState={transitionState}
onClose={onClose}
size="md"
title={iconName}
actions={[
{
text: "Actions",
variant: "primary",
onClick: openOtherMenu
}
]}
>
<Flex className="vc-ic-modal-main">
<div
className="vc-ic-icon-preview"
aria-label={colorData?.name}
onContextMenu={openColorMenu}
onWheel={onWheel}
>
<Icon className="vc-ic-icon-large" color={colorData?.css} fill={fill} />
</div>
<div className="vc-ic-modal-header-trailing">
<CloseButton onClick={onClose} />
</div>
</ModalHeader>
<ModalContent className="vc-ic-modal-content">
<Flex className="vc-ic-modal-main">
<div
className="vc-ic-icon-preview"
aria-label={colorData?.name}
onContextMenu={openColorMenu}
onWheel={onWheel}
>
<Icon className="vc-ic-icon-large" color={colorData?.css} fill={fill} />
</div>
<Flex flexDirection="column" className="vc-ic-icon-info">
<Flex className="vc-ic-icon-sizes">
{iconSizes.map(size => (
<TooltipContainer text={size} key={size}>
<Icon size={size} color={colorData?.css} fill={fill} />
</TooltipContainer>
))}
</Flex>
<TooltipContainer text="Right-click icon to change">
<BaseText size="sm" color="text-muted" className="vc-ic-color-label">
{colorData?.name}
</BaseText>
</TooltipContainer>
<Flex flexDirection="column" className="vc-ic-icon-info">
<Flex className="vc-ic-icon-sizes">
{iconSizes.map(size => (
<TooltipContainer text={size} key={size}>
<Icon size={size} color={colorData?.css} fill={fill} />
</TooltipContainer>
))}
</Flex>
<TooltipContainer text="Right-click icon to change">
<BaseText size="sm" color="text-muted" className="vc-ic-color-label">
{colorData?.name}
</BaseText>
</TooltipContainer>
</Flex>
<div className="vc-ic-use-as">
<BaseText size="md" weight="semibold">Usage</BaseText>
<BaseText size="sm" color="text-muted">Click to copy</BaseText>
</div>
{/* for some reason i cant make this shit codeblock full width, FF 15 */}
<Clickable className="vc-ic-codeblock-wrapper" onClick={() => copyWithToast(findCode, "Copied!")}>
<CodeBlock content={findCode} lang="ts" />
</Clickable>
</ModalContent>
<ModalFooter className="vc-ic-modal-footer">
<Button variant="primary" onClick={openOtherMenu}>
Actions
</Button>
</ModalFooter>
</ModalRoot>
</Flex>
<div className="vc-ic-use-as">
<BaseText size="md" weight="semibold">Usage</BaseText>
<BaseText size="sm" color="text-muted">Click to copy</BaseText>
</div>
{/* for some reason i cant make this shit codeblock full width, FF 15 */}
<Clickable className="vc-ic-codeblock-wrapper" onClick={() => copyWithToast(findCode, "Copied!")}>
<CodeBlock content={findCode} lang="ts" />
</Clickable>
</Modal>
);
}

View file

@ -7,38 +7,35 @@
import ErrorBoundary from "@components/ErrorBoundary";
import { Paragraph } from "@components/Paragraph";
import { classNameFactory } from "@utils/css";
import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { GuildMember } from "@vencord/discord-types";
import { Parser } from "@webpack/common";
import { GuildMember, RenderModalProps } from "@vencord/discord-types";
import { Modal,openModal, Parser, React } from "@webpack/common";
const cl = classNameFactory("vc-inrole-");
export function showInRoleModal(members: GuildMember[], roleId: string, channelId: string) {
openModal(props =>
<>
<ErrorBoundary>
<ModalRoot {...props} size={ModalSize.DYNAMIC} fullscreenOnMobile={true} >
<ModalHeader className={cl("header")}>
<Paragraph style={{ fontSize: "1.2rem", fontWeight: "bold", marginRight: "7px" }}>Members of role {
Parser.parse(`<@&${roleId}>`, true, { channelId, viewingChannelId: channelId })
} ({members.length})</Paragraph>
<ModalCloseButton onClick={props.onClose} className={cl("close")} />
</ModalHeader>
<ModalContent>
<div style={{ padding: "13px 20px" }} className={cl("member-list")}>
{
members.length !== 0 ? members.map(member =>
<>
<Paragraph className={cl("modal-member")}>
{Parser.parse(`<@${member.userId}>`, true, { channelId, viewingChannelId: channelId })}
</Paragraph>
</>
) : <Paragraph>Looks like no online cached members with that role were found. Try scrolling down on your member list to cache more users!</Paragraph>
}
</div>
</ModalContent>
</ModalRoot>
</ErrorBoundary>
</>
openModal((props: RenderModalProps) =>
<ErrorBoundary>
<Modal
{...props}
size="md"
title={
<Paragraph style={{ fontSize: "1.2rem", fontWeight: "bold", marginRight: "7px" }}>
Members of role {Parser.parse(`<@&${roleId}>`, true, { channelId, viewingChannelId: channelId })} ({members.length})
</Paragraph>
}
>
<div style={{ padding: "13px 20px" }} className={cl("member-list")}>
{
members.length !== 0 ? members.map(member =>
<React.Fragment key={member.userId}>
<Paragraph className={cl("modal-member")}>
{Parser.parse(`<@${member.userId}>`, true, { channelId, viewingChannelId: channelId })}
</Paragraph>
</React.Fragment>
) : <Paragraph>Looks like no online cached members with that role were found. Try scrolling down on your member list to cache more users!</Paragraph>
}
</div>
</Modal>
</ErrorBoundary>
);
}

View file

@ -16,59 +16,49 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Heading, HeadingTertiary } from "@components/Heading";
import {
ModalContent,
ModalFooter,
ModalHeader,
ModalRoot,
openModal,
} from "@utils/modal";
import { Button, React, TextInput } from "@webpack/common";
import { Heading } from "@components/Heading";
import { RenderModalProps } from "@vencord/discord-types";
import { Modal, openModal, React, TextInput } from "@webpack/common";
import { buildEmbed, decrypt } from "../index";
export function DecModal(props: any) {
export function DecModal(props: RenderModalProps & { message: any; }) {
const encryptedMessage: string = props?.message?.content;
const [password, setPassword] = React.useState("password");
const onDecrypt = () => {
const toSend = decrypt(encryptedMessage, password, true);
if (!toSend || !props?.message) return;
buildEmbed(props?.message, toSend);
props.onClose();
};
return (
<ModalRoot {...props}>
<ModalHeader>
<HeadingTertiary>Decrypt Message</HeadingTertiary>
</ModalHeader>
<ModalContent>
<Heading style={{ marginTop: "10px" }}>Message with Encryption</Heading>
<TextInput defaultValue={encryptedMessage} disabled={true}></TextInput>
<Heading style={{ marginTop: "10px" }}>Password</Heading>
<TextInput
style={{ marginBottom: "20px" }}
onChange={setPassword}
/>
</ModalContent>
<ModalFooter>
<Button
color={Button.Colors.GREEN}
onClick={() => {
const toSend = decrypt(encryptedMessage, password, true);
if (!toSend || !props?.message) return;
buildEmbed(props?.message, toSend);
props.onClose();
}}>
Decrypt
</Button>
<Button
color={Button.Colors.TRANSPARENT}
look={Button.Looks.LINK}
style={{ left: 15, position: "absolute" }}
onClick={props.onClose}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>
<Modal
{...props}
size="sm"
title="Decrypt Message"
actions={[
{
text: "Decrypt",
variant: "primary",
onClick: onDecrypt
},
{
text: "Cancel",
variant: "secondary",
onClick: props.onClose
}
]}
>
<Heading style={{ marginTop: "10px" }}>Message with Encryption</Heading>
<TextInput defaultValue={encryptedMessage} disabled={true}></TextInput>
<Heading style={{ marginTop: "10px" }}>Password</Heading>
<TextInput
style={{ marginBottom: "20px" }}
onChange={setPassword}
/>
</Modal>
);
}

View file

@ -17,21 +17,14 @@
*/
import { FormSwitch } from "@components/FormSwitch";
import { Heading, HeadingTertiary } from "@components/Heading";
import { Heading } from "@components/Heading";
import { insertTextIntoChatInputBox } from "@utils/discord";
import {
ModalContent,
ModalFooter,
ModalHeader,
ModalProps,
ModalRoot,
openModal,
} from "@utils/modal";
import { Button, React, TextInput } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Modal, openModal, React, TextInput } from "@webpack/common";
import { encrypt } from "../index";
function EncModal(props: ModalProps) {
function EncModal(props: RenderModalProps) {
const [secret, setSecret] = React.useState("");
const [cover, setCover] = React.useState("");
const [password, setPassword] = React.useState("password");
@ -39,72 +32,65 @@ function EncModal(props: ModalProps) {
const isValid = secret && (noCover || (cover && cover.trim().split(" ").length > 1));
const onSend = () => {
if (!isValid) return;
const encrypted = encrypt(secret, password, noCover ? "d d" : cover);
const toSend = noCover ? encrypted.replaceAll("d", "") : encrypted;
if (!toSend) return;
insertTextIntoChatInputBox(toSend);
props.onClose();
};
return (
<ModalRoot {...props}>
<ModalHeader>
<HeadingTertiary>Encrypt Message</HeadingTertiary>
</ModalHeader>
<ModalContent>
<Heading style={{ marginTop: "10px" }}>Secret</Heading>
<TextInput
onChange={(e: string) => {
setSecret(e);
}}
/>
<Heading style={{ marginTop: "10px" }}>Cover (2 or more Words!!)</Heading>
<TextInput
disabled={noCover}
onChange={(e: string) => {
setCover(e);
}}
/>
<Heading style={{ marginTop: "10px" }}>Password</Heading>
<TextInput
style={{ marginBottom: "20px" }}
defaultValue={"password"}
onChange={(e: string) => {
setPassword(e);
}}
/>
<FormSwitch
title="Don't use a Cover"
value={noCover}
onChange={(e: boolean) => {
setNoCover(e);
}}
/>
</ModalContent>
<ModalFooter>
<Button
color={Button.Colors.GREEN}
disabled={!isValid}
onClick={() => {
if (!isValid) return;
const encrypted = encrypt(secret, password, noCover ? "d d" : cover);
const toSend = noCover ? encrypted.replaceAll("d", "") : encrypted;
if (!toSend) return;
insertTextIntoChatInputBox(toSend);
props.onClose();
}}
>
Send
</Button>
<Button
color={Button.Colors.TRANSPARENT}
look={Button.Looks.LINK}
style={{ left: 15, position: "absolute" }}
onClick={() => {
props.onClose();
}}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>
<Modal
{...props}
size="sm"
title="Encrypt Message"
actions={[
{
text: "Send",
variant: "primary",
disabled: !isValid,
onClick: onSend
},
{
text: "Cancel",
variant: "secondary",
onClick: props.onClose
}
]}
>
<Heading style={{ marginTop: "10px" }}>Secret</Heading>
<TextInput
onChange={(e: string) => {
setSecret(e);
}}
/>
<Heading style={{ marginTop: "10px" }}>Cover (2 or more Words!!)</Heading>
<TextInput
disabled={noCover}
onChange={(e: string) => {
setCover(e);
}}
/>
<Heading style={{ marginTop: "10px" }}>Password</Heading>
<TextInput
style={{ marginBottom: "20px" }}
defaultValue={"password"}
onChange={(e: string) => {
setPassword(e);
}}
/>
<FormSwitch
title="Don't use a Cover"
value={noCover}
onChange={(e: boolean) => {
setNoCover(e);
}}
/>
</Modal>
);
}

View file

@ -9,14 +9,14 @@ import "./style.css";
import { actions } from "@equicordplugins/keyboardNavigation/commands";
import { classNameFactory } from "@utils/css";
import { Logger } from "@utils/Logger";
import { closeAllModals, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { React, TextInput, useEffect, useState } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { closeAllModals, Modal,openModal, React, TextInput, useEffect, useState } from "@webpack/common";
import { settings } from "..";
const logger = new Logger("CommandPalette", "#e5c890");
export function CommandPalette({ modalProps }) {
export function CommandPalette({ modalProps }: { modalProps: RenderModalProps; }) {
const cl = classNameFactory("vc-command-palette-");
const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
const [startIndex, setStartIndex] = useState(0);
@ -100,8 +100,8 @@ export function CommandPalette({ modalProps }) {
}, [queryEh]);
return (
<ModalRoot className={cl("root")} {...modalProps} size={ModalSize.MEDIUM} onKeyDown={handleKeyDown} onWheel={handleWheel}>
<div>
<Modal {...modalProps} size="md" title="Command Palette">
<div className={cl("root")} onKeyDown={handleKeyDown} onWheel={handleWheel}>
<TextInput
value={queryEh}
onChange={e => setQuery(e)}
@ -123,7 +123,7 @@ export function CommandPalette({ modalProps }) {
))}
</div>
</div>
</ModalRoot>
</Modal>
);
}

View file

@ -8,13 +8,13 @@ import "./style.css";
import { ButtonAction } from "@equicordplugins/keyboardNavigation/commands";
import { classNameFactory } from "@utils/css";
import { closeAllModals, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { React, TextInput, useEffect, useState } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { closeAllModals, Modal,openModal, React, TextInput, useEffect, useState } from "@webpack/common";
import { settings } from "..";
interface MultipleChoiceProps {
modalProps: ModalProps;
modalProps: RenderModalProps;
onSelect: (selectedValue: any) => void;
choices: ButtonAction[];
}
@ -99,9 +99,8 @@ export function MultipleChoice({ modalProps, onSelect, choices }: MultipleChoice
}, [queryEh]);
return (
// @ts-ignore
<ModalRoot className={cl("root")} {...modalProps} size={ModalSize.MEDIUM} onKeyDown={handleKeyDown} onWheel={handleWheel}>
<div>
<Modal {...modalProps} size="md" title="Multiple Choice">
<div className={cl("root")} onKeyDown={handleKeyDown} onWheel={handleWheel}>
<TextInput
value={queryEh}
onChange={e => setQuery(e)}
@ -123,7 +122,7 @@ export function MultipleChoice({ modalProps, onSelect, choices }: MultipleChoice
))}
</div>
</div>
</ModalRoot>
</Modal>
);
}

View file

@ -6,11 +6,11 @@
import "./style.css";
import { closeAllModals, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { React, TextInput, useEffect, useState } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { closeAllModals, Modal,openModal, React, TextInput, useEffect, useState } from "@webpack/common";
interface SimpleTextInputProps {
modalProps: ModalProps;
modalProps: RenderModalProps;
onSelect: (inputValue: string) => void;
placeholder?: string;
info?: string;
@ -35,16 +35,17 @@ export function SimpleTextInput({ modalProps, onSelect, placeholder, info }: Sim
}, []);
return (
// @ts-ignore
<ModalRoot className="vc-command-palette-simple-text" {...modalProps} size={ModalSize.DYNAMIC} onKeyDown={handleKeyDown}>
<TextInput
value={inputValue}
onChange={e => setInputValue(e as unknown as string)}
style={{ width: "30vw", borderRadius: "5px" }}
placeholder={placeholder ?? "Type and press Enter"}
/>
{info && <div className="vc-command-palette-textinfo">{info}</div>}
</ModalRoot>
<Modal {...modalProps} size="sm" title="Text Input">
<div className="vc-command-palette-simple-text" onKeyDown={handleKeyDown}>
<TextInput
value={inputValue}
onChange={e => setInputValue(e as unknown as string)}
style={{ width: "30vw", borderRadius: "5px" }}
placeholder={placeholder ?? "Type and press Enter"}
/>
{info && <div className="vc-command-palette-textinfo">{info}</div>}
</div>
</Modal>
);
}

View file

@ -7,9 +7,8 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { closeAllModals } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { SettingsRouter, useState } from "@webpack/common";
import { closeAllModals,SettingsRouter, useState } from "@webpack/common";
import { registerAction } from "./commands";
import { openCommandPalette } from "./components/CommandPalette";

View file

@ -9,9 +9,6 @@ import { QrCodeIcon } from "@components/Icons";
import { wrapTab } from "@components/settings";
import loginWithQR from "@equicordplugins/loginWithQR";
import { images } from "@equicordplugins/loginWithQR/images";
import {
ModalProps,
} from "@utils/modal";
import { findByPropsLazy } from "@webpack";
import {
RestAPI,
@ -135,7 +132,7 @@ const handleProcessImage = (file: File, modalPropsRef: QrModalPropsRef) => {
reader.readAsDataURL(file);
};
function QrModal(props: ModalProps) {
function QrModal() {
const [state, setState] = useState(LoginStateType.Idle);
const [preview, setPreview] = useState<Preview | null>(null);
const error = useRef<string | null>(null);

View file

@ -8,21 +8,15 @@ import { BaseText } from "@components/BaseText";
import { Button, TextButton } from "@components/Button";
import { images } from "@equicordplugins/loginWithQR/images";
import { getIntlMessage } from "@utils/discord";
import {
ModalContent,
ModalFooter,
ModalProps,
ModalRoot,
ModalSize,
openModal,
} from "@utils/modal";
import { RenderModalProps } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack";
import {
Modal,
openModal,
RestAPI,
useEffect,
useRef,
useState,
} from "@webpack/common";
useState } from "@webpack/common";
import { cl } from "..";
@ -41,7 +35,7 @@ function VerifyModal({
}: {
token: string | null;
onAbort: () => void;
} & ModalProps) {
} & RenderModalProps) {
const [state, setState] = useState(
!token ? VerifyState.NotFound : VerifyState.Verifying
);
@ -112,8 +106,8 @@ function VerifyModal({
}, [state]);
return (
<ModalRoot size={ModalSize.DYNAMIC} {...props}>
<ModalContent scrollbarType="none" className={cl("device-content")}>
<Modal size="sm" {...props} title="Verify Login">
<div className={cl("device-content")}>
{state === VerifyState.LoggedIn ? (
<>
<img
@ -191,7 +185,7 @@ function VerifyModal({
variant="dangerPrimary"
className={cl("device-confirm")}
style={{
["--duration" as any]: `${holdDuration}ms`,
["--progress" as any]: `${holdDuration}ms`,
}}
onPointerDown={startInput}
onPointerUp={endInput}
@ -202,8 +196,8 @@ function VerifyModal({
</Button>
</>
)}
</ModalContent>
<ModalFooter className={cl("device-footer")}>
</div>
<div className={cl("device-footer")} style={{ marginTop: "20px", display: "flex", justifyContent: "flex-end", gap: "10px" }}>
{state === VerifyState.LoggedIn ? (
<Button onClick={props.onClose}>
{getIntlMessage("QR_CODE_LOGIN_FINISH_BUTTON")}
@ -218,8 +212,8 @@ function VerifyModal({
: getIntlMessage("CANCEL")}
</TextButton>
)}
</ModalFooter>
</ModalRoot>
</div>
</Modal>
);
}

View file

@ -8,13 +8,11 @@ import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import { Flex } from "@components/Flex";
import { InfoIcon } from "@components/Icons";
import { Link } from "@components/Link";
import { copyWithToast, openUserProfile } from "@utils/discord";
import { closeAllModals, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { LazyComponent } from "@utils/react";
import { type User } from "@vencord/discord-types";
import { RenderModalProps, type User } from "@vencord/discord-types";
import { find, findByCode, findByCodeLazy } from "@webpack";
import { Alerts, ChannelStore, ContextMenuApi, FluxDispatcher, GuildStore, Menu, NavigationRouter, React, TabBar, TextInput, Tooltip, useMemo, useRef, useState } from "@webpack/common";
import { Alerts, ChannelStore, closeAllModals, ContextMenuApi, FluxDispatcher, GuildStore, Menu, Modal, NavigationRouter, openModal, React, TabBar, TextInput, Tooltip, useMemo, useRef, useState } from "@webpack/common";
import { DBMessageRecord, deleteMessageIDB, deleteMessagesBulkIDB } from "../db";
import { cl, clearLogs, settings } from "../index";
@ -62,7 +60,7 @@ export enum LogTabs {
}
interface Props {
modalProps: ModalProps;
modalProps: RenderModalProps;
initalQuery?: string;
}
@ -76,46 +74,96 @@ export function LogsModal({ modalProps, initalQuery }: Props) {
const { messages, total, statusTotal, pending, reset } = useMessages(queryEh, currentTab, sortNewest, numDisplayedMessages);
return (
<ModalRoot className={cl("modal-root")} {...modalProps} size={ModalSize.LARGE}>
<ModalHeader className={cl("modal-header")}>
<TextInput value={queryEh} onChange={e => setQuery(e)} style={{ width: "100%" }} placeholder="Filter Messages" />
<TabBar
type="top"
look="brand"
className={cl("modal-tab-bar")}
selectedItem={currentTab}
onItemSelect={e => {
setCurrentTab(e);
setNumDisplayedMessages(settings.store.messagesToDisplayAtOnceInLogs);
<Modal
{...modalProps}
size="lg"
title={
<div style={{ display: "flex", flexDirection: "column", gap: "10px", width: "100%", paddingRight: "20px" }}>
<TextInput value={queryEh} onChange={e => setQuery(e)} style={{ width: "100%" }} placeholder="Filter Messages" />
<TabBar
type="top"
look="brand"
className={cl("modal-tab-bar")}
selectedItem={currentTab}
onItemSelect={e => {
setCurrentTab(e);
setNumDisplayedMessages(settings.store.messagesToDisplayAtOnceInLogs);
contentRef.current?.firstElementChild?.scrollTo(0, 0);
}}
>
<TabBar.Item
className={cl("modal-tab-bar-item")}
id={LogTabs.DELETED}
>
Deleted
</TabBar.Item>
<TabBar.Item
className={cl("modal-tab-bar-item")}
id={LogTabs.EDITED}
>
Edited
</TabBar.Item>
<TabBar.Item
className={cl("modal-tab-bar-item")}
id={LogTabs.GHOST_PING}
>
Ghost Pinged
</TabBar.Item>
</TabBar>
</div>
}
actions={[
{
text: "Clear All Logs",
variant: "dangerPrimary",
onClick: () => Alerts.show({
title: "Clear Logs",
body: "Are you sure you want to clear all the logs",
confirmText: "Clear",
// @ts-expect-error not typed
confirmVariant: "critical-primary",
cancelText: "Cancel",
onConfirm: async () => {
await clearLogs();
reset();
}
})
},
{
text: "Clear Visible Logs",
variant: "dangerSecondary",
disabled: messages?.length === 0,
onClick: () => Alerts.show({
title: "Clear Logs",
body: `Are you sure you want to clear ${messages.length} logs`,
confirmText: "Clear",
// @ts-expect-error not typed
confirmVariant: "critical-primary",
cancelText: "Cancel",
onConfirm: async () => {
await deleteMessagesBulkIDB(messages.map(e => e.message_id));
reset();
}
})
},
{
text: `Sort ${sortNewest ? "Oldest First" : "Newest First"}`,
variant: "secondary",
onClick: () => {
setSortNewest(e => {
const val = !e;
settings.store.sortNewest = val;
return val;
});
contentRef.current?.firstElementChild?.scrollTo(0, 0);
}}
>
<TabBar.Item
className={cl("modal-tab-bar-item")}
id={LogTabs.DELETED}
>
Deleted
</TabBar.Item>
<TabBar.Item
className={cl("modal-tab-bar-item")}
id={LogTabs.EDITED}
>
Edited
</TabBar.Item>
<TabBar.Item
className={cl("modal-tab-bar-item")}
id={LogTabs.GHOST_PING}
>
Ghost Pinged
</TabBar.Item>
</TabBar>
</ModalHeader>
<div style={{ opacity: modalProps.transitionState === 1 ? "1" : "0" }} className={cl("modal-content-container")} ref={contentRef}>
}
}
]}
>
<div style={{ opacity: modalProps.transitionState === 1 ? "1" : "0" }} className={`${cl("modal-content-container")} ${cl("modal-root")}`} ref={contentRef}>
{
modalProps.transitionState === 1 &&
<ModalContent
className={cl("modal-content")}
>
<div>
{messages != null && total === 0 && (
<EmptyLogs
hasQuery={queryEh.length !== 0}
@ -133,62 +181,10 @@ export function LogsModal({ modalProps, initalQuery }: Props) {
handleLoadMore={() => setNumDisplayedMessages(e => e + settings.store.messagesToDisplayAtOnceInLogs)}
/>
)}
</ModalContent>
</div>
}
</div>
<ModalFooter className={cl("footer")}>
<Button
variant="dangerPrimary"
onClick={() => Alerts.show({
title: "Clear Logs",
body: "Are you sure you want to clear all the logs",
confirmText: "Clear",
// @ts-expect-error not typed
confirmVariant: "critical-primary",
cancelText: "Cancel",
onConfirm: async () => {
await clearLogs();
reset();
}
})}
>
Clear All Logs
</Button>
<Button
style={{ marginRight: "16px" }}
variant="dangerSecondary"
disabled={messages?.length === 0}
onClick={() => Alerts.show({
title: "Clear Logs",
body: `Are you sure you want to clear ${messages.length} logs`,
confirmText: "Clear",
// @ts-expect-error not typed
confirmVariant: "critical-primary",
cancelText: "Cancel",
onConfirm: async () => {
await deleteMessagesBulkIDB(messages.map(e => e.message_id));
reset();
}
})}
>
Clear Visible Logs
</Button>
<Link
style={{ marginRight: "1rem" }}
onClick={() => {
setSortNewest(e => {
const val = !e;
settings.store.sortNewest = val;
return val;
});
contentRef.current?.firstElementChild?.scrollTo(0, 0);
}}
>
Sort {sortNewest ? "Oldest First" : "Newest First"}
</Link>
</ModalFooter>
</ModalRoot>
</Modal>
);
}

View file

@ -4,13 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { HeadingPrimary } from "@components/Heading";
import { PickerContent, PickerContentHeader, PickerContentRow, PickerContentRowGrid, PickerHeaderProps, SidebarProps, Sticker, StickerCategoryType, StickerPack } from "@equicordplugins/moreStickers/types";
import { sendSticker } from "@equicordplugins/moreStickers/upload";
import { clPicker, FFmpegStateContext } from "@equicordplugins/moreStickers/utils";
import { debounce } from "@shared/debounce";
import { ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { React, TextInput } from "@webpack/common";
import { Modal,openModal, React, TextInput } from "@webpack/common";
import { JSX } from "react";
import { CategoryImage, CategoryScroller, CategoryWrapper, StickerCategory } from "./categories";
@ -72,14 +70,9 @@ export const PickerSidebar = ({ packMetas, onPackSelect }: SidebarProps) => {
onClick={() => {
openModal(modalProps => {
return (
<ModalRoot size={ModalSize.LARGE} {...modalProps}>
<ModalHeader>
<HeadingPrimary>Stickers+</HeadingPrimary>
</ModalHeader>
<ModalContent>
<Packs />
</ModalContent>
</ModalRoot>
<Modal size="lg" title="Stickers+" {...modalProps}>
<Packs />
</Modal>
);
});
}}

View file

@ -9,8 +9,7 @@ import { TooltipContainer } from "@components/TooltipContainer";
import { settings } from "@equicordplugins/musicControls/settings";
import { SpotifyLrcStore } from "@equicordplugins/musicControls/spotify/lyrics/providers/store";
import { SpotifyStore } from "@equicordplugins/musicControls/spotify/SpotifyStore";
import { openModal } from "@utils/modal";
import { ContextMenuApi, React, useEffect, useState, useStateFromStores } from "@webpack/common";
import { ContextMenuApi, openModal,React, useEffect, useState, useStateFromStores } from "@webpack/common";
import { LyricsContextMenu } from "./ctxMenu";
import { LyricsModal } from "./modal";

View file

@ -7,8 +7,8 @@
import { BaseText } from "@components/BaseText";
import { SpotifyStore, Track } from "@equicordplugins/musicControls/spotify/SpotifyStore";
import { openImageModal } from "@utils/discord";
import { ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { React } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Modal,React } from "@webpack/common";
import { cl, NoteSvg, scrollClasses, useLyrics } from "./util";
@ -18,72 +18,63 @@ const formatTime = (time: number) => {
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
};
function ModalHeaderContent({ track }: { track: Track | null; }) {
function getTitleNode(track: Track | null) {
if (!track) {
return (
<ModalHeader>
<BaseText size="sm" weight="semibold">No track playing</BaseText>
</ModalHeader>
);
return <BaseText size="sm" weight="semibold">No track playing</BaseText>;
}
return (
<ModalHeader>
<div className={cl("header-content")}>
{track?.album?.image?.url && (
<img
src={track.album.image.url}
alt={track.album.name}
className={cl("album-image")}
onClick={() => openImageModal({
url: track.album.image.url,
width: track.album.image.width,
height: track.album.image.height,
})}
/>
)}
<div>
<BaseText size="sm" weight="semibold">{track.name}</BaseText>
<BaseText size="sm">by {track.artists.map(a => a.name).join(", ")}</BaseText>
<BaseText size="sm">on {track.album.name}</BaseText>
</div>
<div className={cl("header-content")}>
{track?.album?.image?.url && (
<img
src={track.album.image.url}
alt={track.album.name}
className={cl("album-image")}
onClick={() => openImageModal({
url: track.album.image.url,
width: track.album.image.width,
height: track.album.image.height,
})}
/>
)}
<div>
<BaseText size="sm" weight="semibold">{track.name}</BaseText>
<BaseText size="sm">by {track.artists.map(a => a.name).join(", ")}</BaseText>
<BaseText size="sm">on {track.album.name}</BaseText>
</div>
</ModalHeader>
</div>
);
}
const modalCurrentLine = cl("modal-line-current");
const modalLine = cl("modal-line");
export function LyricsModal({ props }: { props: ModalProps; }) {
export function LyricsModal({ props }: { props: RenderModalProps; }) {
const { track, lyricsInfo, currLrcIndex } = useLyrics({ scroll: false });
const currentLyrics = lyricsInfo?.lyricsVersions[lyricsInfo.useLyric];
return (
<ModalRoot {...props}>
<ModalHeaderContent track={track} />
<ModalContent>
<div className={`${cl("lyrics-modal-container")} ${scrollClasses.auto}`}>
{currentLyrics ? (
currentLyrics.map((line, i) => (
<BaseText
key={i}
size={currLrcIndex === i ? "md" : "sm"}
weight={currLrcIndex === i ? "semibold" : "normal"}
className={currLrcIndex === i ? modalCurrentLine : modalLine}
>
<span className={cl("modal-timestamp")} onClick={() => SpotifyStore.seek(line.time * 1000)}>
{formatTime(line.time)}
</span>
{line.text || NoteSvg()}
</BaseText>
))
) : (
<BaseText size="sm" className={cl("modal-no-lyrics")}>
No lyrics available :(
<Modal {...props} size="md" title={getTitleNode(track)}>
<div className={`${cl("lyrics-modal-container")} ${scrollClasses.auto}`}>
{currentLyrics ? (
currentLyrics.map((line, i) => (
<BaseText
key={i}
size={currLrcIndex === i ? "md" : "sm"}
weight={currLrcIndex === i ? "semibold" : "normal"}
className={currLrcIndex === i ? modalCurrentLine : modalLine}
>
<span className={cl("modal-timestamp")} onClick={() => SpotifyStore.seek(line.time * 1000)}>
{formatTime(line.time)}
</span>
{line.text || NoteSvg()}
</BaseText>
)}
</div>
</ModalContent>
</ModalRoot>
))
) : (
<BaseText size="sm" className={cl("modal-no-lyrics")}>
No lyrics available :(
</BaseText>
)}
</div>
</Modal>
);
}

View file

@ -9,8 +9,7 @@ import { TooltipContainer } from "@components/TooltipContainer";
import { settings } from "@equicordplugins/musicControls/settings";
import { TidalLrcStore } from "@equicordplugins/musicControls/tidal/lyrics/providers/store";
import { TidalStore } from "@equicordplugins/musicControls/tidal/TidalStore";
import { openModal } from "@utils/modal";
import { ContextMenuApi, useEffect, useState, useStateFromStores } from "@webpack/common";
import { ContextMenuApi, openModal,useEffect, useState, useStateFromStores } from "@webpack/common";
import { LyricsContextMenu } from "./ctxMenu";
import { LyricsModal } from "./modal";

View file

@ -6,7 +6,8 @@
import { BaseText } from "@components/BaseText";
import { TidalStore, Track } from "@equicordplugins/musicControls/tidal/TidalStore";
import { ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { RenderModalProps } from "@vencord/discord-types";
import { Modal,React } from "@webpack/common";
import { cl, NoteSvg, scrollClasses, useLyrics } from "./util";
@ -16,58 +17,54 @@ const formatTime = (time: number) => {
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
};
function ModalHeaderContent({ track }: { track: Track; }) {
function getTitleNode(track: Track | null) {
if (!track) return null;
return (
<ModalHeader>
<div className={cl("header-content")}>
{track?.imageSrc && (
<img
src={track.imageSrc}
alt={track.album || track.name}
className={cl("album-image")}
/>
)}
<div>
<BaseText size="sm" weight="semibold">{track.name}</BaseText>
<BaseText size="sm">by {track.artist}</BaseText>
{track.album && <BaseText size="sm">on {track.album}</BaseText>}
</div>
<div className={cl("header-content")}>
{track?.imageSrc && (
<img
src={track.imageSrc}
alt={track.album || track.name}
className={cl("album-image")}
/>
)}
<div>
<BaseText size="sm" weight="semibold">{track.name}</BaseText>
<BaseText size="sm">by {track.artist}</BaseText>
{track.album && <BaseText size="sm">on {track.album}</BaseText>}
</div>
</ModalHeader>
</div>
);
}
export function LyricsModal({ rootProps }: { rootProps: ModalProps; }) {
export function LyricsModal({ rootProps }: { rootProps: RenderModalProps; }) {
const { track, lyrics, currLrcIndex } = useLyrics({ scroll: false });
const currentLyrics = lyrics || null;
const position = track ? TidalStore.mPosition : 0;
return (
<ModalRoot {...rootProps}>
{track && <ModalHeaderContent track={track} />}
<ModalContent>
<div className={cl("lyrics-modal-container") + ` ${scrollClasses.auto}`}>
{currentLyrics ? (
currentLyrics.map((line, i) => (
<BaseText
key={i}
size={currLrcIndex === i ? "md" : "sm"}
weight={currLrcIndex === i ? "semibold" : "normal"}
className={currLrcIndex === i ? cl("modal-line-current") : cl("modal-line")}
<Modal {...rootProps} size="md" title={getTitleNode(track)}>
<div className={cl("lyrics-modal-container") + ` ${scrollClasses.auto}`}>
{currentLyrics ? (
currentLyrics.map((line, i) => (
<BaseText
key={i}
size={currLrcIndex === i ? "md" : "sm"}
weight={currLrcIndex === i ? "semibold" : "normal"}
className={currLrcIndex === i ? cl("modal-line-current") : cl("modal-line")}
>
<span className={cl("modal-timestamp")}
onClick={() => TidalStore.seek(line.time * 1000)}
>
<span className={cl("modal-timestamp")}
onClick={() => TidalStore.seek(line.time * 1000)}
>
{formatTime(line.time)}
</span>
{line.text || NoteSvg(cl("modal-note"))}
</BaseText>
))
) : (
<BaseText size="sm" className={cl("modal-no-lyrics")}>No lyrics available :(</BaseText>
)}
</div>
</ModalContent>
</ModalRoot>
{formatTime(line.time)}
</span>
{line.text || NoteSvg(cl("modal-note"))}
</BaseText>
))
) : (
<BaseText size="sm" className={cl("modal-no-lyrics")}>No lyrics available :(</BaseText>
)}
</div>
</Modal>
);
}

View file

@ -10,15 +10,14 @@ import { Settings, useSettings } from "@api/Settings";
import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { PluginDependencyList } from "@components/settings/tabs/plugins";
import { PluginCard } from "@components/settings/tabs/plugins/PluginCard";
import { ChangeList } from "@utils/ChangeList";
import { classNameFactory } from "@utils/css";
import { CloseButton, closeModal, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useForceUpdater } from "@utils/react";
import { RenderModalProps } from "@vencord/discord-types";
import { findComponentByCodeLazy } from "@webpack";
import { Tooltip, useMemo } from "@webpack/common";
import { closeModal, Modal,openModal, Tooltip, useMemo } from "@webpack/common";
import { ReactNode } from "react";
import Plugins from "~plugins";
@ -32,7 +31,7 @@ const Checkbox = findComponentByCodeLazy('"data-toggleable-component":"checkbox'
let hasSeen = false;
interface ModalComponentProps {
modalProps: ModalProps;
modalProps: RenderModalProps;
newPlugins: Set<string>;
newSettings: KnownPluginSettingsMap;
}
@ -121,8 +120,10 @@ function NewPluginsModal({ modalProps, newPlugins, newSettings }: ModalComponent
};
return (
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<ModalHeader separator={false} className={cl("header")}>
<Modal
{...modalProps}
size="md"
title={
<div className={cl("header-content")}>
<BaseText size="lg" weight="semibold" className={cl("title")}>
New Plugins and Settings ({totalCount})
@ -131,53 +132,46 @@ function NewPluginsModal({ modalProps, newPlugins, newSettings }: ModalComponent
New plugins have been added since your last visit. Enable any you'd like or continue to dismiss.
</BaseText>
</div>
<div className={cl("header-trailing")}>
<CloseButton onClick={modalProps.onClose} />
</div>
</ModalHeader>
}
>
<div className={cl("grid")}>
{pluginCards}
{requiredPluginCards}
</div>
<ModalContent>
<div className={cl("grid")}>
{pluginCards}
{requiredPluginCards}
</div>
</ModalContent>
<div className={cl("footer")} style={{ marginTop: "20px", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Tooltip
text={
<>
The following plugins require a restart:
<ul className={cl("restart-list")}>
{changes.map(p => <li key={p}>{p}</li>)}
</ul>
</>
}
shouldShow={changes.hasChanges}
>
{tooltipProps => (
<Button
{...tooltipProps}
onClick={handleContinue}
>
{changes.hasChanges ? "Restart" : "Continue"}
</Button>
)}
</Tooltip>
<ModalFooter>
<Flex className={cl("footer")}>
<Tooltip
text={
<>
The following plugins require a restart:
<ul className={cl("restart-list")}>
{changes.map(p => <li key={p}>{p}</li>)}
</ul>
</>
}
shouldShow={changes.hasChanges}
>
{tooltipProps => (
<Button
{...tooltipProps}
onClick={handleContinue}
>
{changes.hasChanges ? "Restart" : "Continue"}
</Button>
)}
</Tooltip>
<Checkbox
type="inverted"
value={!settings?.plugins?.NewPluginsManager?.enabled}
onChange={() => {
Settings.plugins.NewPluginsManager.enabled = !settings?.plugins?.NewPluginsManager?.enabled;
}}
>
Don't show this again
</Checkbox>
</Flex>
</ModalFooter>
</ModalRoot>
<Checkbox
type="inverted"
value={!settings?.plugins?.NewPluginsManager?.enabled}
onChange={() => {
Settings.plugins.NewPluginsManager.enabled = !settings?.plugins?.NewPluginsManager?.enabled;
}}
>
Don't show this again
</Checkbox>
</div>
</Modal>
);
}

View file

@ -4,11 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Button } from "@components/Button";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
interface ConfirmModalProps extends ModalProps {
import { RenderModalProps } from "@vencord/discord-types";
import { Modal, React } from "@webpack/common";
interface ConfirmModalProps extends RenderModalProps {
title: string;
message: string;
confirmText: string;
@ -17,7 +17,7 @@ interface ConfirmModalProps extends ModalProps {
onCancel: () => void;
}
interface ImportProfilesModalProps extends ModalProps {
interface ImportProfilesModalProps extends RenderModalProps {
title: string;
message: string;
onOverride: () => void;
@ -25,57 +25,65 @@ interface ImportProfilesModalProps extends ModalProps {
onCancel: () => void;
}
export function ConfirmModal({ title, message, confirmText, cancelText, onConfirm, onCancel, onClose, transitionState }: ConfirmModalProps) {
export function ConfirmModal({ title, message, confirmText, cancelText, onConfirm, onCancel, ...props }: ConfirmModalProps) {
const closeAfter = (action: () => void) => () => {
action();
onClose();
props.onClose();
};
return (
<ModalRoot transitionState={transitionState}>
<ModalHeader>
<Heading tag="h2">{title}</Heading>
</ModalHeader>
<ModalContent>
<Paragraph>{message}</Paragraph>
</ModalContent>
<ModalFooter>
<Button onClick={closeAfter(onConfirm)}>
{confirmText}
</Button>
<Button variant="secondary" onClick={closeAfter(onCancel)}>
{cancelText}
</Button>
</ModalFooter>
</ModalRoot>
<Modal
{...props}
size="sm"
title={title}
actions={[
{
text: confirmText,
variant: "primary",
onClick: closeAfter(onConfirm)
},
{
text: cancelText,
variant: "secondary",
onClick: closeAfter(onCancel)
}
]}
>
<Paragraph>{message}</Paragraph>
</Modal>
);
}
export function ImportProfilesModal({ title, message, onOverride, onMerge, onCancel, onClose, transitionState }: ImportProfilesModalProps) {
export function ImportProfilesModal({ title, message, onOverride, onMerge, onCancel, ...props }: ImportProfilesModalProps) {
const closeAfter = (action: () => void) => () => {
action();
onClose();
props.onClose();
};
return (
<ModalRoot transitionState={transitionState}>
<ModalHeader>
<Heading tag="h2">{title}</Heading>
</ModalHeader>
<ModalContent>
<Paragraph>{message}</Paragraph>
</ModalContent>
<ModalFooter>
<Button onClick={closeAfter(onOverride)}>
Override
</Button>
<Button onClick={closeAfter(onMerge)}>
Merge
</Button>
<Button variant="secondary" onClick={closeAfter(onCancel)}>
Cancel
</Button>
</ModalFooter>
</ModalRoot>
<Modal
{...props}
size="sm"
title={title}
actions={[
{
text: "Override",
variant: "primary",
onClick: closeAfter(onOverride)
},
{
text: "Merge",
variant: "primary",
onClick: closeAfter(onMerge)
},
{
text: "Cancel",
variant: "secondary",
onClick: closeAfter(onCancel)
}
]}
>
<Paragraph>{message}</Paragraph>
</Modal>
);
}

View file

@ -7,8 +7,7 @@
import { Button } from "@components/Button";
import { Heading } from "@components/Heading";
import { classes } from "@utils/misc";
import { openModal } from "@utils/modal";
import { React, SelectedGuildStore, TextInput, useStateFromStores } from "@webpack/common";
import { openModal,React, SelectedGuildStore, TextInput, useStateFromStores } from "@webpack/common";
import { cl, settings } from "../index";
import { exportPresets, ImportDecision, importPresets, savePreset } from "../utils/actions";

View file

@ -5,8 +5,8 @@
*/
import PluginModal from "@components/settings/tabs/plugins/PluginModal";
import { openModal } from "@utils/modal";
import type { Plugin } from "@utils/types";
import { openModal } from "@webpack/common";
import { promptToRestartIfDirty } from "../settings/restartTracking";
import { setSettingsModalOpen } from "../state";

View file

@ -5,14 +5,12 @@
*/
import { definePluginSettings } from "@api/Settings";
import { BaseText } from "@components/BaseText";
import { FormSwitch } from "@components/FormSwitch";
import { Devs, EquicordDevs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { Message } from "@vencord/discord-types";
import { Button, IconUtils, Menu, TextInput, UploadHandler, useEffect, useState } from "@webpack/common";
import { Message, RenderModalProps } from "@vencord/discord-types";
import { IconUtils, Menu, Modal, openModal, TextInput, UploadHandler, useEffect, useState } from "@webpack/common";
import { QuoteIcon } from "./components/QuoteIcon";
import { QuoteFont } from "./types";
@ -89,7 +87,7 @@ export default definePlugin({
}
});
function QuoteModal({ message, ...props }: ModalProps & { message: Message; }) {
function QuoteModal({ message, ...props }: RenderModalProps & { message: Message; }) {
const [gray, setGray] = useState(settings.store.grayscale);
const [showWatermark, setShowWatermark] = useState(settings.store.showWatermark);
const [saveAsGif, setSaveAsGif] = useState(settings.store.saveAsGif);
@ -166,49 +164,48 @@ function QuoteModal({ message, ...props }: ModalProps & { message: Message; }) {
};
return (
<ModalRoot {...props} size={ModalSize.MEDIUM}>
<ModalHeader separator={false}>
<BaseText color="text-strong" size="lg" weight="semibold" tag="h1" style={{ flexGrow: 1 }}>
Catch Them In 4K.
</BaseText>
<ModalCloseButton onClick={props.onClose} />
</ModalHeader>
<ModalContent scrollbarType="none">
<img alt="Quote preview" src="" id="quoterPreview" style={{ borderRadius: "20px", width: "100%", marginBottom: "20px" }} />
<Modal
{...props}
size="md"
title="Catch Them In 4K."
actions={[
{
text: "Export",
variant: "primary",
onClick: handleExport
},
{
text: "Send",
variant: "primary",
onClick: handleSendInChat
}
]}
>
<img alt="Quote preview" src="" id="quoterPreview" style={{ borderRadius: "20px", width: "100%", marginBottom: "20px" }} />
<FormSwitch title="Grayscale" value={gray} onChange={setGray} />
<FormSwitch
title="Save as GIF"
value={saveAsGif}
onChange={setSaveAsGif}
description="Saves/Sends the image as a GIF instead of a PNG"
/>
<FormSwitch
title="Show Watermark"
value={showWatermark}
onChange={setShowWatermark}
hideBorder={showWatermark}
/>
{showWatermark && (
<div style={{ marginTop: "8px", marginBottom: "20px" }}>
<TextInput
value={watermarkText}
onChange={setWatermarkText}
placeholder="Watermark text (max 32 characters)"
maxLength={32}
/>
</div>
)}
<div style={{ display: "flex", gap: "8px", marginTop: "16px", marginBottom: "16px" }}>
<Button color={Button.Colors.BRAND} size={Button.Sizes.MEDIUM} onClick={handleExport}>
Export
</Button>
<Button color={Button.Colors.BRAND} size={Button.Sizes.MEDIUM} onClick={handleSendInChat}>
Send
</Button>
<FormSwitch title="Grayscale" value={gray} onChange={setGray} />
<FormSwitch
title="Save as GIF"
value={saveAsGif}
onChange={setSaveAsGif}
description="Saves/Sends the image as a GIF instead of a PNG"
/>
<FormSwitch
title="Show Watermark"
value={showWatermark}
onChange={setShowWatermark}
hideBorder={showWatermark}
/>
{showWatermark && (
<div style={{ marginTop: "8px", marginBottom: "20px" }}>
<TextInput
value={watermarkText}
onChange={setWatermarkText}
placeholder="Watermark text (max 32 characters)"
maxLength={32}
/>
</div>
</ModalContent>
</ModalRoot>
)}
</Modal>
);
}

View file

@ -10,9 +10,8 @@ import * as DataStore from "@api/DataStore";
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs, IS_MAC } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { closeModal, openModal } from "@utils/modal";
import definePlugin, { makeRange, OptionType } from "@utils/types";
import { Button, ChannelRouter, ChannelStore, IconUtils, React, RelationshipStore, SelectedChannelStore, Toasts, UserStore } from "@webpack/common";
import { Button, ChannelRouter, ChannelStore, closeModal, IconUtils, openModal,React, RelationshipStore, SelectedChannelStore, Toasts, UserStore } from "@webpack/common";
const STORAGE_KEY = "RDMSwitch_history";

View file

@ -4,9 +4,8 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { BaseText } from "@components/BaseText";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
import { Button } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Modal, React } from "@webpack/common";
import { sendRemix } from ".";
import { brushCanvas, canvas, cropCanvas, ctx, exportImg, shapeCanvas } from "./editor/components/Canvas";
@ -15,7 +14,7 @@ import { resetBounds } from "./editor/tools/crop";
import { SendIcon } from "./icons/SendIcon";
type Props = {
modalProps: ModalProps;
modalProps: RenderModalProps;
close: () => void;
url?: string;
};
@ -38,18 +37,28 @@ async function closeModal(closeFunc: () => void, save?: boolean) {
export default function RemixModal({ modalProps, close, url }: Props) {
return (
<ModalRoot {...modalProps} size={ModalSize.LARGE}>
<ModalHeader>
<BaseText size="lg" weight="semibold" style={{ flexGrow: 1 }}>Remix</BaseText>
<ModalCloseButton onClick={() => closeModal(close)} />
</ModalHeader>
<ModalContent>
<Editor url={url} />
</ModalContent>
<ModalFooter className="vc-remix-modal-footer">
<Button onClick={() => closeModal(close, true)} className="vc-remix-send"><SendIcon /> Send</Button>
<Button onClick={() => closeModal(close)} color={Button.Colors.RED}>Close</Button>
</ModalFooter>
</ModalRoot>
<Modal
{...modalProps}
size="lg"
title="Remix"
actions={[
{
text: (
<span style={{ display: "flex", alignItems: "center", gap: "6px" }}>
<SendIcon /> Send
</span>
) as any,
variant: "primary",
onClick: () => closeModal(close, true)
},
{
text: "Close",
variant: "dangerPrimary",
onClick: () => closeModal(close)
}
]}
>
<Editor url={url} />
</Modal>
);
}

View file

@ -8,10 +8,9 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co
import { disableStyle, enableStyle } from "@api/Styles";
import { PaintbrushIcon } from "@components/Icons";
import { EquicordDevs } from "@utils/constants";
import { closeModal, openModal } from "@utils/modal";
import definePlugin from "@utils/types";
import { extractAndLoadChunksLazy } from "@webpack";
import { ChannelStore, DraftType, FluxDispatcher, Menu, PendingReplyStore, SelectedChannelStore, UploadHandler } from "@webpack/common";
import { ChannelStore, closeModal, DraftType, FluxDispatcher, Menu, openModal,PendingReplyStore, SelectedChannelStore, UploadHandler } from "@webpack/common";
import RemixModal from "./RemixModal";
import css from "./styles.css?managed";

View file

@ -8,9 +8,9 @@ import { Button } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary";
import { Heading } from "@components/Heading";
import { classNameFactory } from "@utils/css";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import { RenderModalProps } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack";
import { ChannelStore, DraftType, showToast, TextInput, Toasts, UploadManager, useState } from "@webpack/common";
import { ChannelStore, closeModal, DraftType, Modal, openModal, showToast, TextInput, Toasts, UploadManager, useState } from "@webpack/common";
import { ScheduledAttachment } from "../types";
import { addScheduledMessage, getChannelDisplayInfo } from "../utils";
@ -23,7 +23,7 @@ function ScheduleTimeModalInner({ channelId, content, attachments, rootProps, cl
channelId: string;
content: string;
attachments?: ScheduledAttachment[];
rootProps: ModalProps;
rootProps: RenderModalProps;
close: () => void;
}) {
const [scheduleType, setScheduleType] = useState<"delay" | "time">("delay");
@ -69,74 +69,78 @@ function ScheduleTimeModalInner({ channelId, content, attachments, rootProps, cl
};
return (
<ModalRoot {...rootProps}>
<ModalHeader className={cl("modal-header")}>
<Heading tag="h2" className={cl("modal-title")}>Schedule Message</Heading>
<ModalCloseButton onClick={close} />
</ModalHeader>
<Modal
{...rootProps}
size="sm"
title="Schedule Message"
actions={[
{
text: "Schedule",
variant: "primary",
onClick: handleSchedule
},
{
text: "Cancel",
variant: "secondary",
onClick: close
}
]}
>
<div className={cl("channel-info")}>
{avatar && <img src={avatar} className={cl("channel-avatar")} alt="" />}
<span className={cl("channel-text")}>
Scheduling for: <strong>{isDM ? name : `#${name}`}</strong>
</span>
</div>
<ModalContent className={cl("modal-content")}>
<div className={cl("channel-info")}>
{avatar && <img src={avatar} className={cl("channel-avatar")} alt="" />}
<span className={cl("channel-text")}>
Scheduling for: <strong>{isDM ? name : `#${name}`}</strong>
</span>
<Heading tag="h5" className={cl("field-label")}>Schedule Type</Heading>
<div className={cl("schedule-type-buttons")}>
<Button
size="small"
variant={scheduleType === "delay" ? "primary" : "secondary"}
onClick={() => setScheduleType("delay")}
>
Delay
</Button>
<Button
size="small"
variant={scheduleType === "time" ? "primary" : "secondary"}
onClick={() => setScheduleType("time")}
>
Specific Time
</Button>
</div>
{scheduleType === "delay" ? (
<>
<Heading tag="h5" className={cl("field-label")}>Delay (minutes)</Heading>
<TextInput
value={delayMinutes}
onChange={setDelayMinutes}
placeholder="5"
type="number"
/>
</>
) : (
<>
<Heading tag="h5" className={cl("field-label")}>Date & Time</Heading>
<input
type="datetime-local"
className={cl("datetime-input")}
value={scheduledDateTime}
onChange={e => setScheduledDateTime(e.target.value)}
min={new Date().toISOString().slice(0, 16)}
/>
</>
)}
{error && (
<div className={cl("error")}>
<ErrorIcon />
<span>{error}</span>
</div>
<Heading tag="h5" className={cl("field-label")}>Schedule Type</Heading>
<div className={cl("schedule-type-buttons")}>
<Button
size="small"
variant={scheduleType === "delay" ? "primary" : "secondary"}
onClick={() => setScheduleType("delay")}
>
Delay
</Button>
<Button
size="small"
variant={scheduleType === "time" ? "primary" : "secondary"}
onClick={() => setScheduleType("time")}
>
Specific Time
</Button>
</div>
{scheduleType === "delay" ? (
<>
<Heading tag="h5" className={cl("field-label")}>Delay (minutes)</Heading>
<TextInput
value={delayMinutes}
onChange={setDelayMinutes}
placeholder="5"
type="number"
/>
</>
) : (
<>
<Heading tag="h5" className={cl("field-label")}>Date & Time</Heading>
<input
type="datetime-local"
className={cl("datetime-input")}
value={scheduledDateTime}
onChange={e => setScheduledDateTime(e.target.value)}
min={new Date().toISOString().slice(0, 16)}
/>
</>
)}
{error && (
<div className={cl("error")}>
<ErrorIcon />
<span>{error}</span>
</div>
)}
</ModalContent>
<ModalFooter className={cl("modal-footer")}>
<Button onClick={handleSchedule} variant="positive">Schedule</Button>
<Button variant="secondary" onClick={close}>Cancel</Button>
</ModalFooter>
</ModalRoot>
)}
</Modal>
);
}

View file

@ -6,10 +6,9 @@
import { Button } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary";
import { Heading } from "@components/Heading";
import { classNameFactory } from "@utils/css";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import { ChannelStore, showToast, Toasts, useState } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { ChannelStore, closeModal, Modal, openModal, showToast, Toasts, useState } from "@webpack/common";
import { clearAllScheduledMessages, getChannelDisplayInfo, getScheduledMessages, removeScheduledMessage } from "../utils";
import { CalendarIcon, TimerIcon } from "./Icons";
@ -17,7 +16,7 @@ import { CalendarIcon, TimerIcon } from "./Icons";
const cl = classNameFactory("vc-scheduled-msg-");
interface ViewScheduledModalProps {
rootProps: ModalProps;
rootProps: RenderModalProps;
close: () => void;
}
@ -36,69 +35,74 @@ function ViewScheduledModalInner({ rootProps, close }: ViewScheduledModalProps)
showToast("All scheduled messages cleared", Toasts.Type.SUCCESS);
};
const actions = [
{
text: "Close",
variant: "secondary",
onClick: close
}
];
if (messages.length > 0) {
actions.unshift({
text: "Clear All",
variant: "dangerPrimary",
onClick: handleClearAll
});
}
return (
<ModalRoot {...rootProps}>
<ModalHeader className={cl("modal-header")}>
<Heading tag="h2" className={cl("modal-title")}>Scheduled Messages</Heading>
{messages.length > 0 && (
<Button size="small" variant="dangerPrimary" onClick={handleClearAll}>
Clear All
</Button>
)}
<ModalCloseButton onClick={close} />
</ModalHeader>
<Modal
{...rootProps}
size="md"
title="Scheduled Messages"
actions={actions}
>
{!messages.length ? (
<div className={cl("empty-state")}>
<CalendarIcon width={48} height={48} />
<span>No scheduled messages</span>
</div>
) : (
<div className={cl("message-list")}>
{messages.map(msg => {
const { name, avatar } = getChannelDisplayInfo(msg.channelId);
const channel = ChannelStore.getChannel(msg.channelId);
if (!channel) return null;
<ModalContent className={cl("modal-content")}>
{!messages.length ? (
<div className={cl("empty-state")}>
<CalendarIcon width={48} height={48} />
<span>No scheduled messages</span>
</div>
) : (
<div className={cl("message-list")}>
{messages.map(msg => {
const { name, avatar } = getChannelDisplayInfo(msg.channelId);
const channel = ChannelStore.getChannel(msg.channelId);
if (!channel) return null;
const isDM = channel.isPrivate();
const displayContent = msg.content.length > 200
? msg.content.slice(0, 200) + "..."
: msg.content;
const isDM = channel.isPrivate();
const displayContent = msg.content.length > 200
? msg.content.slice(0, 200) + "..."
: msg.content;
return (
<div key={msg.id} className={cl("message-item")}>
<div className={cl("message-info")}>
<div className={cl("message-header")}>
{avatar && <img src={avatar} className={cl("message-avatar")} alt="" />}
<span className={cl("message-channel")}>
{isDM ? name : `#${name}`}
</span>
</div>
<div className={cl("message-time")}>
<TimerIcon width={14} height={14} />
<span>{new Date(msg.scheduledTime).toLocaleString()}</span>
</div>
<div className={cl("message-content")}>{displayContent}</div>
return (
<div key={msg.id} className={cl("message-item")}>
<div className={cl("message-info")}>
<div className={cl("message-header")}>
{avatar && <img src={avatar} className={cl("message-avatar")} alt="" />}
<span className={cl("message-channel")}>
{isDM ? name : `#${name}`}
</span>
</div>
<Button
size="small"
variant="dangerPrimary"
onClick={() => handleDelete(msg.id)}
>
Delete
</Button>
<div className={cl("message-time")}>
<TimerIcon width={14} height={14} />
<span>{new Date(msg.scheduledTime).toLocaleString()}</span>
</div>
<div className={cl("message-content")}>{displayContent}</div>
</div>
);
})}
</div>
)}
</ModalContent>
<ModalFooter className={cl("modal-footer")}>
<Button onClick={close} variant="secondary">Close</Button>
</ModalFooter>
</ModalRoot>
<Button
size="small"
variant="dangerPrimary"
onClick={() => handleDelete(msg.id)}
>
Delete
</Button>
</div>
);
})}
</div>
)}
</Modal>
);
}

View file

@ -4,13 +4,12 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { BaseText } from "@components/BaseText";
import { Flex } from "@components/Flex";
import { characters } from "@equicordplugins/sekaiStickers/characters.json";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
import { React, ScrollerThin, TextInput } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Modal,React, ScrollerThin, TextInput } from "@webpack/common";
export default function CharSelectModal({ modalProps, setCharacter }: { modalProps: ModalProps; setCharacter?: any; }) {
export default function CharSelectModal({ modalProps, setCharacter }: { modalProps: RenderModalProps; setCharacter?: any; }) {
const [search, setSearch] = React.useState<string>("");
const memoedSearchChar = React.useMemo(() => {
@ -30,22 +29,15 @@ export default function CharSelectModal({ modalProps, setCharacter }: { modalPro
});
}, [search, characters]);
return (
<ModalRoot {...modalProps} size={ModalSize.DYNAMIC}>
<ModalHeader>
<BaseText size="lg" weight="bold" style={{ flexGrow: 1 }}>Select character menu</BaseText>
<ModalCloseButton onClick={modalProps.onClose} ></ModalCloseButton>
</ModalHeader>
<ModalContent>
<Flex flexDirection="column" style={{ paddingTop: 12 }}>
<TextInput content="mafuyu" placeholder="Mafuyu" onChange={(e: string) => setSearch(e)} />
<ScrollerThin style={{ height: 520 }}>
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 330px)", rowGap: 6, columnGap: 5, gridTemplateRows: "repeat(3, 256px)" }}>
{memoedSearchChar}
</div>
</ScrollerThin>
</Flex>
</ModalContent>
</ModalRoot>
<Modal {...modalProps} size="lg" title="Select character menu">
<Flex flexDirection="column" style={{ paddingTop: 12 }}>
<TextInput content="mafuyu" placeholder="Mafuyu" onChange={(e: string) => setSearch(e)} />
<ScrollerThin style={{ height: 520 }}>
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 330px)", rowGap: 6, columnGap: 5, gridTemplateRows: "repeat(3, 256px)" }}>
{memoedSearchChar}
</div>
</ScrollerThin>
</Flex>
</Modal>
);
}

View file

@ -4,18 +4,17 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { BaseText } from "@components/BaseText";
import { Flex } from "@components/Flex";
import { FormSwitch } from "@components/FormSwitch";
import { Heading } from "@components/Heading";
import { characters } from "@equicordplugins/sekaiStickers/characters.json";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Button, ChannelStore, React, SelectedChannelStore, Slider, TextArea, UploadHandler } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { ChannelStore, Modal, openModal, React, SelectedChannelStore, Slider, TextArea, UploadHandler } from "@webpack/common";
import Canvas from "./Canvas";
import CharSelectModal from "./Picker";
export default function SekaiStickersModal({ modalProps, settings }: { modalProps: ModalProps; settings: any; }) {
export default function SekaiStickersModal({ modalProps, settings }: { modalProps: RenderModalProps; settings: any; }) {
const [text, setText] = React.useState<string>("奏でーかわいい");
const [character, setChracter] = React.useState<number>(49);
const [fontSize, setFontSize] = React.useState<number>(characters[character].defaultText.s);
@ -97,45 +96,51 @@ export default function SekaiStickersModal({ modalProps, settings }: { modalProp
}
};
return (
<ModalRoot {...modalProps} size={ModalSize.DYNAMIC}>
<ModalHeader>
<BaseText size="lg" weight="bold" style={{ flexGrow: 1 }}>Sekai Stickers</BaseText>
<ModalCloseButton onClick={modalProps.onClose} ></ModalCloseButton>
</ModalHeader>
<ModalContent>
<Flex flexDirection="row" style={{ paddingTop: 12 }}>
<div style={{ marginRight: 30 }}>
<Canvas draw={draw} id="SekaiCard_Canvas" />
<Heading>Text Y Pos</Heading>
<Slider minValue={0} maxValue={256} asValueChanges={va => { va = Math.round(va); setPosition({ x: position.x, y: curve ? 256 + fontSize * 3 - va : 256 - va }); }} initialValue={curve ? 256 - position.y + fontSize * 3 : 256 - position.y} orientation={"vertical"} onValueRender={va => String(Math.round(va))} />
<Heading>Text XZ Pos</Heading>
<Slider minValue={0} maxValue={296} asValueChanges={va => { va = Math.round(va); setPosition({ y: position.y, x: va }); }} initialValue={position.x} orientation={"horizontal"} onValueRender={(v: number) => String(Math.round(v))} />
</div>
<div style={{ marginRight: 10, width: "30vw" }}>
<Heading>Text</Heading>
<TextArea onChange={setText} placeholder={text} rows={4} />
<Heading>Rotation</Heading>
<Slider markers={[-10, -5, 0, 5, 10]} stickToMarkers={false} minValue={-10} maxValue={10} asValueChanges={val => setRotate(val)} initialValue={rotate} keyboardStep={0.2} orientation={"horizontal"} onValueRender={(v: number) => String(v.toFixed(2))} />
<Heading>Font Size</Heading>
<Slider minValue={10} asValueChanges={val => setFontSize(Math.round(val))} maxValue={100} initialValue={fontSize} keyboardStep={1} orientation={"horizontal"} onValueRender={(v: number) => String(Math.round(v))} />
<Heading>Spacing</Heading>
<Slider markers={[18, 36, 72, 100]} stickToMarkers={false} minValue={18} maxValue={100} initialValue={spaceSize} asValueChanges={e => setSpaceSize(e)} onValueRender={e => String(Math.round(e))} />
<FormSwitch title="Enable curve" value={curve} onChange={val => setCurve(val)} />
<Button onClick={() => openModal(props => <CharSelectModal modalProps={props} setCharacter={setChracter} />)}>Switch Character</Button>
</div>
</Flex>
</ModalContent>
<ModalFooter>
<Flex flexDirection="row" style={{ gap: 12 }}>
<Button onClick={() => {
<Modal
{...modalProps}
size="lg"
title="Sekai Stickers"
actions={[
{
text: "Switch Character",
variant: "primary",
onClick: () => {
openModal(props => <CharSelectModal modalProps={props} setCharacter={setChracter} />);
}
},
{
text: "Upload as Attachment",
variant: "primary",
onClick: () => {
if (settings.store.AutoCloseModal) modalProps.onClose();
canvast.toBlob(blob => {
const file = new File([blob as Blob], `${characters[character].character}-sekai_cards.png`, { type: "image/png" });
UploadHandler.promptToUpload([file], ChannelStore.getChannel(SelectedChannelStore.getChannelId()), 0);
});
}}>Upload as Attachment</Button>
</Flex>
</ModalFooter>
</ModalRoot>
}
}
]}
>
<Flex flexDirection="row" style={{ paddingTop: 12 }}>
<div style={{ marginRight: 30 }}>
<Canvas draw={draw} id="SekaiCard_Canvas" />
<Heading>Text Y Pos</Heading>
<Slider minValue={0} maxValue={256} asValueChanges={va => { va = Math.round(va); setPosition({ x: position.x, y: curve ? 256 + fontSize * 3 - va : 256 - va }); }} initialValue={curve ? 256 - position.y + fontSize * 3 : 256 - position.y} orientation={"vertical"} onValueRender={va => String(Math.round(va))} />
<Heading>Text XZ Pos</Heading>
<Slider minValue={0} maxValue={296} asValueChanges={va => { va = Math.round(va); setPosition({ y: position.y, x: va }); }} initialValue={position.x} orientation={"horizontal"} onValueRender={(v: number) => String(Math.round(v))} />
</div>
<div style={{ marginRight: 10, width: "30vw" }}>
<Heading>Text</Heading>
<TextArea onChange={setText} placeholder={text} rows={4} />
<Heading>Rotation</Heading>
<Slider markers={[-10, -5, 0, 5, 10]} stickToMarkers={false} minValue={-10} maxValue={10} asValueChanges={val => setRotate(val)} initialValue={rotate} keyboardStep={0.2} orientation={"horizontal"} onValueRender={(v: number) => String(v.toFixed(2))} />
<Heading>Font Size</Heading>
<Slider minValue={10} asValueChanges={val => setFontSize(Math.round(val))} maxValue={100} initialValue={fontSize} keyboardStep={1} orientation={"horizontal"} onValueRender={(v: number) => String(Math.round(v))} />
<Heading>Spacing</Heading>
<Slider markers={[18, 36, 72, 100]} stickToMarkers={false} minValue={18} maxValue={100} initialValue={spaceSize} asValueChanges={e => setSpaceSize(e)} onValueRender={e => String(Math.round(e))} />
<FormSwitch title="Enable curve" value={curve} onChange={val => setCurve(val)} />
</div>
</Flex>
</Modal>
);
}

View file

@ -7,8 +7,8 @@
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { openModal } from "@webpack/common";
import SekaiStickersModal from "./Components/SekaiStickersModal";
import { kanadeSvg } from "./kanade.svg";

View file

@ -8,9 +8,9 @@ import { AudioPlayerInterface, createAudioPlayer } from "@api/AudioPlayer";
import { HeaderBarButton } from "@api/HeaderBar";
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { ModalProps, ModalRoot, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { React } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Modal,openModal, React } from "@webpack/common";
let boopSound: AudioPlayerInterface;
let song: AudioPlayerInterface;
@ -26,7 +26,7 @@ function assignBoop(url: string, volume: number) {
boopSound.load();
}
function SoggyModal(props: ModalProps) {
function SoggyModal(props: RenderModalProps) {
if (settings.store.songVolume !== 0) {
React.useEffect(() => {
song?.loop();
@ -54,14 +54,13 @@ function SoggyModal(props: ModalProps) {
};
return (
<ModalRoot {...props}>
<Modal {...props} size="dynamic" title="Soggy Cat">
<img
src={settings.store.imageLink}
onClick={boop}
style={{ display: "block" }}
/>
</ModalRoot >
</Modal>
);
}

View file

@ -4,9 +4,8 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { openModal } from "@utils/modal";
import { ApplicationIntegrationType } from "@vencord/discord-types/enums";
import { OAuth2AuthorizeModal, showToast, Toasts } from "@webpack/common";
import { OAuth2AuthorizeModal, openModal,showToast, Toasts } from "@webpack/common";
import { apiConstants, authFetch, getData } from "./api";
import { useAuthorizationStore } from "./stores/AuthorizationStore";

View file

@ -5,7 +5,6 @@
*/
import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { PlusIcon } from "@components/Icons";
@ -13,11 +12,11 @@ import { cl, logger } from "@equicordplugins/songSpotlight.desktop/lib/utils";
import { Native } from "@equicordplugins/songSpotlight.desktop/service";
import { parsers } from "@song-spotlight/api/handlers";
import { Song } from "@song-spotlight/api/structs";
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import { Clickable, TextInput, useState } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Clickable, closeModal, Modal, openModal, TextInput, useState } from "@webpack/common";
interface AddSongModalProps {
modalProps: ModalProps;
modalProps: RenderModalProps;
close(): void;
onAdd(song: Song): string | undefined;
}
@ -29,64 +28,60 @@ function AddSongModal({ modalProps, close, onAdd }: AddSongModalProps) {
return (
<ErrorBoundary>
<ModalRoot {...modalProps}>
<ModalHeader>
<BaseText size="xl" weight="bold">Add a new song</BaseText>
</ModalHeader>
<ModalContent>
<Flex flexDirection="column" gap="10px">
<BaseText size="md" weight="normal" className={cl("sub")}>
Song Spotlight supports these services: <b>{parsers.map(x => x.label).join(", ")}</b>
</BaseText>
<TextInput
placeholder="https://open.spotify.com/..."
error={error}
onChange={value => {
setURL(value);
try {
if (value) new URL(value);
setError(undefined);
} catch {
setError("Invalid URL");
<Modal
{...modalProps}
size="md"
title="Add a new song"
actions={[
{
text: "Add song",
variant: "primary",
disabled: !url || !!error || pending,
onClick: async () => {
setPending(true);
try {
const parsed = await Native.parseLink(url);
if (!parsed) {
setError("Invalid link");
return setPending(false);
}
}}
/>
</Flex>
</ModalContent>
<ModalFooter>
<Flex justifyContent="flex-end" gap={0}>
<Button
variant="primary"
onClick={async () => {
setPending(true);
try {
const parsed = await Native.parseLink(url);
if (!parsed) {
setError("Invalid link");
return setPending(false);
}
const result = onAdd(parsed);
if (result) {
setError(result);
return setPending(false);
}
close();
} catch (error) {
logger.error("parseLink error", error);
setError("Failed to parse link");
setPending(false);
const result = onAdd(parsed);
if (result) {
setError(result);
return setPending(false);
}
}}
disabled={!url || !!error || pending}
>
Add song
</Button>
</Flex>
</ModalFooter>
</ModalRoot>
close();
} catch (error) {
logger.error("parseLink error", error);
setError("Failed to parse link");
setPending(false);
}
}
}
]}
>
<Flex flexDirection="column" gap="10px">
<BaseText size="md" weight="normal" className={cl("sub")}>
Song Spotlight supports these services: <b>{parsers.map(x => x.label).join(", ")}</b>
</BaseText>
<TextInput
placeholder="https://open.spotify.com/..."
error={error}
onChange={value => {
setURL(value);
try {
if (value) new URL(value);
setError(undefined);
} catch {
setError("Invalid URL");
}
}}
/>
</Flex>
</Modal>
</ErrorBoundary>
);
}

View file

@ -20,8 +20,8 @@ import { UserData, UserDataSchema } from "@song-spotlight/api/structs";
import { sid } from "@song-spotlight/api/util";
import { readClipboard } from "@utils/clipboard";
import { copyWithToast } from "@utils/discord";
import { ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Alerts, Parser, showToast, Toasts, useCallback, useEffect, useMemo, useRef, useState } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Alerts, Modal,openModal, Parser, showToast, Toasts, useCallback, useEffect, useMemo, useRef, useState } from "@webpack/common";
interface ImportButtonProps {
overwrite: boolean;
@ -210,19 +210,14 @@ export default function Settings({ templateData }: SettingsProps) {
);
}
export function SettingsModal({ modalProps, ...props }: SettingsProps & { modalProps: ModalProps; }) {
export function SettingsModal({ modalProps, ...props }: SettingsProps & { modalProps: RenderModalProps; }) {
return (
<ErrorBoundary>
<ModalRoot {...modalProps} size={ModalSize.LARGE}>
<ModalHeader>
<BaseText size="xl" weight="bold">Song Spotlight</BaseText>
</ModalHeader>
<ModalContent>
<div style={{ marginBottom: "20px" }}>
<Settings {...props} />
</div>
</ModalContent>
</ModalRoot>
<Modal {...modalProps} size="lg" title="Song Spotlight">
<div style={{ marginBottom: "20px" }}>
<Settings {...props} />
</div>
</Modal>
</ErrorBoundary>
);
}

View file

@ -23,11 +23,10 @@ import { getUserSettingLazy } from "@api/UserSettings";
import ErrorBoundary from "@components/ErrorBoundary";
import { EquicordDevs } from "@utils/constants";
import { proxyLazy } from "@utils/lazy";
import { openModalLazy } from "@utils/modal";
import { useForceUpdater } from "@utils/react";
import definePlugin, { OptionType, StartAt } from "@utils/types";
import { extractAndLoadChunksLazy, findComponentByCodeLazy, findModuleId, wreq } from "@webpack";
import { Menu, OverridePremiumTypeStore, Toasts } from "@webpack/common";
import { Menu, openModalLazy,OverridePremiumTypeStore, Toasts } from "@webpack/common";
interface Emoji {
animated: boolean;

View file

@ -7,8 +7,7 @@
import * as DataStore from "@api/DataStore";
import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger";
import { openModal } from "@utils/modal";
import { OAuth2AuthorizeModal, showToast, Toasts, UserStore, zustandCreate, zustandPersist } from "@webpack/common";
import { OAuth2AuthorizeModal, openModal,showToast, Toasts, UserStore, zustandCreate, zustandPersist } from "@webpack/common";
import { AUTHORIZE_URL, CLIENT_ID } from "../constants";
import { useStreaksStore } from "./StreaksStore";

View file

@ -8,14 +8,13 @@ import { generateId } from "@api/Commands";
import { Settings } from "@api/Settings";
import { Button } from "@components/Button";
import { Card } from "@components/Card";
import { HeadingPrimary, HeadingTertiary } from "@components/Heading";
import { HeadingPrimary } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import type { Theme, ThemeLikeProps } from "@equicordplugins/themeLibrary/types";
import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins";
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { User } from "@vencord/discord-types";
import { FluxDispatcher, Parser, React, UserStore, UserUtils } from "@webpack/common";
import { FluxDispatcher, Modal, openModal, Parser, React, UserStore, UserUtils } from "@webpack/common";
import { Constructor } from "type-fest";
import { LikesComponent } from "./LikesComponent";
@ -65,36 +64,32 @@ export const ThemeCard: React.FC<ThemeCardProps> = ({ theme, themeLinks, likedTh
if (requiresThemeAttributes && !Settings.plugins.ThemeAttributes.enabled) {
openModal(modalProps => (
<ModalRoot {...modalProps} size={ModalSize.SMALL}>
<ModalHeader>
<HeadingTertiary>Hold on!</HeadingTertiary>
</ModalHeader>
<ModalContent>
<Paragraph style={{ padding: "8px" }}>
<p>This theme requires the <b>ThemeAttributes</b> plugin to work properly!</p>
<p>Do you want to enable it?</p>
</Paragraph>
</ModalContent>
<ModalFooter>
<Button
variant="positive"
onClick={() => {
<Modal
{...modalProps}
size="sm"
title="Hold on!"
actions={[
{
text: "Enable Plugin",
variant: "primary",
onClick: () => {
Settings.plugins.ThemeAttributes.enabled = true;
modalProps.onClose();
handleAddRemoveTheme();
}}
>
Enable Plugin
</Button>
<Button
variant="dangerPrimary"
className={Margins.right8}
onClick={() => modalProps.onClose()}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>
}
},
{
text: "Cancel",
variant: "secondary",
onClick: () => modalProps.onClose()
}
]}
>
<Paragraph style={{ padding: "8px" }}>
<p>This theme requires the <b>ThemeAttributes</b> plugin to work properly!</p>
<p>Do you want to enable it?</p>
</Paragraph>
</Modal>
));
} else {
handleAddRemoveTheme();

View file

@ -5,19 +5,17 @@
*/
import { CodeBlock } from "@components/CodeBlock";
import { Heading, HeadingTertiary } from "@components/Heading";
import { Heading } from "@components/Heading";
import { Heart } from "@components/Heart";
import { Paragraph } from "@components/Paragraph";
import { Theme, ThemeInfoModalProps } from "@equicordplugins/themeLibrary/types";
import { ClockIcon, DownloadIcon, WarningIcon } from "@equicordplugins/themeLibrary/utils/Icons";
import { ClockIcon, WarningIcon } from "@equicordplugins/themeLibrary/utils/Icons";
import { copyToClipboard } from "@utils/clipboard";
import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import type { PluginNative } from "@utils/types";
import { findComponentByCodeLazy } from "@webpack";
import { Button, Parser, React, showToast, Toasts } from "@webpack/common";
import { Button, Modal, openModal, Parser, React, showToast, Toasts } from "@webpack/common";
import { logger } from "./ThemeTab";
@ -48,212 +46,192 @@ export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, .
const lastUpdated = Math.floor(new Date(last_updated ?? 0).getTime() / 1000);
return (
<ModalRoot {...props}>
<ModalHeader>
<HeadingTertiary>{type} Details</HeadingTertiary>
</ModalHeader>
<ModalContent>
<Heading style={{ marginTop: "10px" }}>{authors.length > 1 ? "Authors" : "Author"}</Heading>
<div style={{ display: "flex", alignItems: "center", marginBottom: "10px" }}>
<div>
<div style={{ display: "flex", alignItems: "center" }}>
<UserSummaryItem
users={authors}
count={authors.length}
guildId={undefined}
renderIcon={false}
max={4}
size={32}
showDefaultAvatarsForNullUsers
showUserPopout
className={Margins.right8}
/>
<Paragraph style={{ maxWidth: "200px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{authors.map(author => author.username).join(", ")}
</Paragraph>
</div>
{version && (
<>
<Heading style={{ marginTop: "10px" }}>Version</Heading>
<Paragraph>
{version}
</Paragraph>
</>
)}
<Heading style={{ marginTop: "10px" }}>Likes</Heading>
<Paragraph>
{likes === 0 ? `Nobody liked this ${type} yet.` : `${likes} users liked this ${type}!`}
</Paragraph>
{donate && (
<>
<Heading style={{ marginTop: "10px" }}>Donate</Heading>
<Paragraph>
You can support the author by donating below!
</Paragraph>
<Paragraph style={{ marginTop: "10px" }}>
<Button onClick={() => VencordNative.native.openExternal(donate)}>
<Heart />
Donate
</Button>
</Paragraph>
</>
)}
{(guild || invite) && (
<>
<Heading style={{ marginTop: "10px" }}>Support Server</Heading>
{guild && (
<Paragraph>
{guild.name}
</Paragraph>
)}
<Paragraph>
<Button
color={Button.Colors.BRAND}
look={Button.Looks.FILLED}
className={Margins.top8}
onClick={async e => {
e.preventDefault();
const useInvite = guild ? guild.invite_link?.split("discord.gg/")[1] : invite;
useInvite != null && openInviteModal(useInvite).catch(() => showToast("Invalid or expired invite!", Toasts.Type.FAILURE));
}}
>
Join Discord Server
</Button>
</Paragraph>
</>
)}
<Heading style={{ marginTop: "10px" }}>Source</Heading>
<Paragraph>
<Button
disabled={!theme.content || theme.id === "preview"}
onClick={() => openModal(modalProps => (
<ModalRoot {...modalProps} size={ModalSize.LARGE}>
<ModalHeader>
<HeadingTertiary>Theme Source</HeadingTertiary>
</ModalHeader>
<ModalContent>
<Paragraph style={{
padding: "8px",
}}>
<CodeBlock lang="css" content={themeContent} />
</Paragraph>
</ModalContent>
<ModalFooter>
<Button
color={Button.Colors.RED}
look={Button.Looks.FILLED}
onClick={() => modalProps.onClose()}
>
Close
</Button>
<Button className={Margins.right8}
onClick={() => {
copyToClipboard(themeContent);
showToast("Copied to Clipboard", Toasts.Type.SUCCESS);
}}>Copy to Clipboard</Button>
</ModalFooter>
</ModalRoot>
))}
>
View Theme Source
</Button>
</Paragraph>
{tags && (
<>
<Heading style={{ marginTop: "10px" }}>Tags</Heading>
<Paragraph>
{tags.map(tag => (
<span className="vce-theme-info-tag" key={"vce-theme-info-tag"}>
{tag}
</span>
))}
</Paragraph>
</>
)}
{requiresThemeAttributes && (
<Paragraph style={{ marginTop: "10px" }}>
<WarningIcon /> This theme requires the <b>ThemeAttributes</b> plugin!
</Paragraph>
)}
{last_updated && (
<Paragraph style={{ marginTop: "10px" }}>
<ClockIcon /> This theme was last updated {Parser.parse("<t:" + lastUpdated + ":F>")} ({Parser.parse("<t:" + lastUpdated + ":R>")})
</Paragraph>
)}
</div>
</div>
</ModalContent>
<ModalFooter>
<Button
color={Button.Colors.RED}
look={Button.Looks.FILLED}
onClick={() => props.onClose()}
>
Close
</Button>
<Button
color={Button.Colors.GREEN}
look={Button.Looks.FILLED}
className={classes("vce-button", Margins.right8)}
disabled={!theme.content || theme.id === "preview"}
onClick={async () => {
<Modal
{...props}
size="md"
title={`${type} Details`}
actions={[
{
text: "Close",
variant: "dangerPrimary",
onClick: () => props.onClose()
},
{
text: "Download",
variant: "positive",
disabled: !theme.content || theme.id === "preview",
onClick: async () => {
const themesDir = await VencordNative.themes.getThemesDir();
const exists = await Native.themeExists(themesDir, theme);
// using another function so we get the proper file path instead of just guessing
// which slash to use (im looking at you windows)
const validThemesDir = await Native.getThemesDir(themesDir, theme);
// check if theme exists, and ask if they want to overwrite
if (exists) {
openModal(modalProps => (
<ModalRoot {...modalProps} size={ModalSize.SMALL}>
<ModalHeader>
<HeadingTertiary>Conflict!</HeadingTertiary>
</ModalHeader>
<ModalContent>
<Paragraph style={{
padding: "8px",
}}>
<div style={{ display: "flex", flexDirection: "column" }}>
<p>A theme with the same name <b>already exist</b> in your themes directory! Do you want to overwrite it?</p>
<div className="vce-overwrite-modal">
<code style={{ wordWrap: "break-word" }}>
{validThemesDir}
</code>
</div>
</div>
</Paragraph>
</ModalContent>
<ModalFooter>
<Button
look={Button.Looks.FILLED}
color={Button.Colors.RED}
onClick={async () => {
<Modal
{...modalProps}
size="sm"
title="Conflict!"
actions={[
{
text: "Overwrite",
variant: "dangerPrimary",
onClick: async () => {
await downloadTheme(themesDir, theme);
modalProps.onClose();
}}
>
Overwrite
</Button>
<Button
color={Button.Colors.GREEN}
look={Button.Looks.FILLED}
className={Margins.right8} onClick={() => modalProps.onClose()}
>
Keep my file
</Button>
</ModalFooter>
</ModalRoot>
}
},
{
text: "Keep my file",
variant: "positive",
onClick: () => modalProps.onClose()
}
]}
>
<Paragraph style={{ padding: "8px" }}>
<div style={{ display: "flex", flexDirection: "column" }}>
<p>A theme with the same name <b>already exists</b> in your themes directory! Do you want to overwrite it?</p>
<div className="vce-overwrite-modal">
<code style={{ wordWrap: "break-word" }}>
{validThemesDir}
</code>
</div>
</div>
</Paragraph>
</Modal>
));
} else {
await downloadTheme(themesDir, theme);
}
}}
>
}
}
]}
>
<Heading style={{ marginTop: "10px" }}>{authors.length > 1 ? "Authors" : "Author"}</Heading>
<div style={{ display: "flex", alignItems: "center", marginBottom: "10px" }}>
<div>
<div style={{ display: "flex", alignItems: "center" }}>
Download <DownloadIcon style={{ marginLeft: "5px" }} />
<UserSummaryItem
users={authors}
count={authors.length}
guildId={undefined}
renderIcon={false}
max={4}
size={32}
showDefaultAvatarsForNullUsers
showUserPopout
className={Margins.right8}
/>
<Paragraph style={{ maxWidth: "200px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{authors.map(author => author.username).join(", ")}
</Paragraph>
</div>
</Button>
</ModalFooter>
</ModalRoot>
{version && (
<>
<Heading style={{ marginTop: "10px" }}>Version</Heading>
<Paragraph>
{version}
</Paragraph>
</>
)}
<Heading style={{ marginTop: "10px" }}>Likes</Heading>
<Paragraph>
{likes === 0 ? `Nobody liked this ${type} yet.` : `${likes} users liked this ${type}!`}
</Paragraph>
{donate && (
<>
<Heading style={{ marginTop: "10px" }}>Donate</Heading>
<Paragraph>
You can support the author by donating below!
</Paragraph>
<Paragraph style={{ marginTop: "10px" }}>
<Button onClick={() => VencordNative.native.openExternal(donate)}>
<Heart />
Donate
</Button>
</Paragraph>
</>
)}
{(guild || invite) && (
<>
<Heading style={{ marginTop: "10px" }}>Support Server</Heading>
{guild && (
<Paragraph>
{guild.name}
</Paragraph>
)}
<Paragraph>
<Button
color={Button.Colors.BRAND}
look={Button.Looks.FILLED}
className={Margins.top8}
onClick={async e => {
e.preventDefault();
const useInvite = guild ? guild.invite_link?.split("discord.gg/")[1] : invite;
useInvite != null && openInviteModal(useInvite).catch(() => showToast("Invalid or expired invite!", Toasts.Type.FAILURE));
}}
>
Join Discord Server
</Button>
</Paragraph>
</>
)}
<Heading style={{ marginTop: "10px" }}>Source</Heading>
<Paragraph>
<Button
disabled={!theme.content || theme.id === "preview"}
onClick={() => openModal(modalProps => (
<Modal
{...modalProps}
size="lg"
title="Theme Source"
actions={[
{
text: "Close",
variant: "dangerPrimary",
onClick: () => modalProps.onClose()
},
{
text: "Copy to Clipboard",
variant: "primary",
onClick: () => {
copyToClipboard(themeContent);
showToast("Copied to Clipboard", Toasts.Type.SUCCESS);
}
}
]}
>
<Paragraph style={{ padding: "8px" }}>
<CodeBlock lang="css" content={themeContent} />
</Paragraph>
</Modal>
))}
>
View Theme Source
</Button>
</Paragraph>
{tags && (
<>
<Heading style={{ marginTop: "10px" }}>Tags</Heading>
<Paragraph>
{tags.map(tag => (
<span className="vce-theme-info-tag" key={"vce-theme-info-tag"}>
{tag}
</span>
))}
</Paragraph>
</>
)}
{requiresThemeAttributes && (
<Paragraph style={{ marginTop: "10px" }}>
<WarningIcon /> This theme requires the <b>ThemeAttributes</b> plugin!
</Paragraph>
)}
{last_updated && (
<Paragraph style={{ marginTop: "10px" }}>
<ClockIcon /> This theme was last updated {Parser.parse("<t:" + lastUpdated + ":F>")} ({Parser.parse("<t:" + lastUpdated + ":R>")})
</Paragraph>
)}
</div>
</div>
</Modal>
);
};

View file

@ -4,8 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ModalProps } from "@utils/modal";
import { User } from "@vencord/discord-types";
import { RenderModalProps, User } from "@vencord/discord-types";
type Author = {
github_name?: string;
@ -35,7 +34,7 @@ export interface Theme {
requiresThemeAttributes?: boolean;
}
export interface ThemeInfoModalProps extends ModalProps {
export interface ThemeInfoModalProps extends RenderModalProps {
author: User | User[];
theme: Theme;
}

View file

@ -7,8 +7,7 @@
import * as DataStore from "@api/DataStore";
import { showNotification } from "@api/Notifications";
import { logger, themeRequest } from "@equicordplugins/themeLibrary/components/ThemeTab";
import { openModal } from "@utils/modal";
import { OAuth2AuthorizeModal, Toasts, UserStore } from "@webpack/common";
import { OAuth2AuthorizeModal, openModal,Toasts, UserStore } from "@webpack/common";
export async function authorizeUser(triggerModal: boolean = true) {
const isAuthorized = await getAuthorization();

View file

@ -5,11 +5,11 @@
*/
import * as DataStore from "@api/DataStore";
import { HeadingPrimary, HeadingSecondary } from "@components/Heading";
import { HeadingSecondary } from "@components/Heading";
import { classNameFactory } from "@utils/css";
import { Margins } from "@utils/margins";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { Button, SearchableSelect, useEffect, useMemo, useState } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { Modal, SearchableSelect, useEffect, useMemo, useState } from "@webpack/common";
import { DATASTORE_KEY, getSystemTimezone, resolveUserTimezone, settings, timezones } from ".";
import { setTimezone, setUserDatabaseTimezone } from "./database";
@ -21,7 +21,7 @@ export async function setUserTimezone(userId: string, timezone: string | null) {
const cl = classNameFactory("vc-timezone-");
export function SetTimezoneModal({ userId, modalProps, database }: { userId: string, modalProps: ModalProps; database?: boolean; }) {
export function SetTimezoneModal({ userId, modalProps, database }: { userId: string, modalProps: RenderModalProps; database?: boolean; }) {
const [currentValue, setCurrentValue] = useState<string | null>(null);
useEffect(() => {
@ -39,63 +39,59 @@ export function SetTimezoneModal({ userId, modalProps, database }: { userId: str
});
}, []);
const actions = [
{
text: "Save",
variant: "primary",
disabled: currentValue === null,
onClick: async () => {
if (database) {
const success = await setTimezone(currentValue!);
if (success) {
await setUserDatabaseTimezone(userId, currentValue);
}
} else {
await setUserTimezone(userId, currentValue);
}
modalProps.onClose();
}
}
];
if (!database) {
actions.unshift({
text: "Delete Timezone",
variant: "dangerPrimary",
disabled: false,
onClick: async () => {
await setUserTimezone(userId, null);
modalProps.onClose();
}
});
}
return (
<ModalRoot {...modalProps}>
<ModalHeader className={cl("modal-header")}>
<HeadingPrimary>
Timezones
</HeadingPrimary>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<Modal
{...modalProps}
size="sm"
title="Timezones"
actions={actions}
>
<section className={Margins.bottom16}>
<HeadingSecondary>
Select Timezone
</HeadingSecondary>
<ModalContent className={cl("modal-content")}>
<section className={Margins.bottom16}>
<HeadingSecondary>
Select Timezone
</HeadingSecondary>
<SearchableSelect
options={options}
value={options.find(o => o.value === currentValue)?.value}
placeholder={"Select a Timezone"}
maxVisibleItems={5}
closeOnSelect={true}
onChange={v => setCurrentValue(v)}
/>
</section>
</ModalContent>
<ModalFooter className={cl("modal-footer")}>
{!database && (
<Button
color={Button.Colors.RED}
onClick={async () => {
await setUserTimezone(userId, null);
modalProps.onClose();
}}
>
Delete Timezone
</Button>
)}
<Button
color={Button.Colors.BRAND}
disabled={currentValue === null}
onClick={async () => {
if (database) {
const success = await setTimezone(currentValue!);
if (success) {
await setUserDatabaseTimezone(userId, currentValue);
}
} else {
await setUserTimezone(userId, currentValue);
}
modalProps.onClose();
}}
>
Save
</Button>
</ModalFooter>
</ModalRoot>
<SearchableSelect
options={options}
value={options.find(o => o.value === currentValue)?.value}
placeholder={"Select a Timezone"}
maxVisibleItems={5}
closeOnSelect={true}
onChange={v => setCurrentValue(v)}
/>
</section>
</Modal>
);
}

View file

@ -11,11 +11,10 @@ import * as DataStore from "@api/DataStore";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, EquicordDevs } from "@utils/constants";
import { openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { Message, User } from "@vencord/discord-types";
import { findByPropsLazy, findCssClassesLazy } from "@webpack";
import { Button, Menu, showToast, Toasts, Tooltip, useEffect, UserStore, useState } from "@webpack/common";
import { Button, Menu, openModal,showToast, Toasts, Tooltip, useEffect, UserStore, useState } from "@webpack/common";
import { deleteTimezone, getTimezone, loadDatabaseTimezones, setUserDatabaseTimezone } from "./database";
import { SetTimezoneModal } from "./TimezoneModal";

View file

@ -5,13 +5,11 @@
*/
import { set } from "@api/DataStore";
import { Button } from "@components/Button";
import { Flex } from "@components/Flex";
import { Heading } from "@components/Heading";
import { Margins } from "@components/margins";
import { classNameFactory } from "@utils/css";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { IconUtils, React, TextInput, Toasts, UserStore, useState } from "@webpack/common";
import { RenderModalProps } from "@vencord/discord-types";
import { IconUtils, Modal, React, TextInput, Toasts, UserStore, useState } from "@webpack/common";
import { data, KEY_DATASTORE } from ".";
@ -26,7 +24,7 @@ function fileToDataUrl(file: File): Promise<string> {
});
}
export function SetAvatarModal({ userId, modalProps }: { userId: string; modalProps: ModalProps; }) {
export function SetAvatarModal({ userId, modalProps }: { userId: string; modalProps: RenderModalProps; }) {
const { avatars } = data;
const user = UserStore.getUser(userId);
const originalAvatar = IconUtils.getUserAvatarURL(user, true, 128) || "";
@ -36,7 +34,7 @@ export function SetAvatarModal({ userId, modalProps }: { userId: string; modalPr
const [isDragging, setIsDragging] = useState(false);
const fileInputRef = React.useRef<HTMLInputElement>(null);
function handleKey(e: KeyboardEvent) {
function handleKey(e: React.KeyboardEvent) {
if (e.key === "Enter") saveUserAvatar();
}
@ -78,15 +76,30 @@ export function SetAvatarModal({ userId, modalProps }: { userId: string; modalPr
modalProps.onClose();
}
const actions = [
{
text: "Save",
variant: "primary",
onClick: saveUserAvatar
}
];
if (avatars[userId]) {
actions.unshift({
text: "Delete",
variant: "dangerPrimary",
onClick: deleteUserAvatar
});
}
return (
<ModalRoot {...modalProps}>
<ModalHeader className={cl("modal-header")}>
<Heading tag="h3">Custom Avatar</Heading>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent className={cl("modal-content")} onKeyDown={handleKey}>
<Modal
{...modalProps}
size="sm"
title="Custom Avatar"
actions={actions}
>
<div onKeyDown={handleKey}>
{/* Preview */}
<div className={cl("preview-row")}>
<div className={cl("preview-box")}>
@ -141,17 +154,7 @@ export function SetAvatarModal({ userId, modalProps }: { userId: string; modalPr
}}
/>
</div>
</ModalContent>
<ModalFooter className={cl("modal-footer")}>
<Flex gap="8px">
{avatars[userId] && (
<Button variant="dangerPrimary" onClick={deleteUserAvatar}>Delete</Button>
)}
<Button onClick={saveUserAvatar}>Save</Button>
</Flex>
</ModalFooter>
</ModalRoot>
</div>
</Modal>
);
}

View file

@ -17,11 +17,10 @@ import { Notice } from "@components/Notice";
import { Devs, EquicordDevs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { openInviteModal } from "@utils/discord";
import { openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types";
import { extractAndLoadChunksLazy } from "@webpack";
import { IconUtils, Menu, UserStore } from "@webpack/common";
import { IconUtils, Menu, openModal,UserStore } from "@webpack/common";
import { SetAvatarModal } from "./AvatarModal";

View file

@ -19,9 +19,8 @@ import {
wrapTab,
} from "@components/settings/tabs/BaseTab";
import { classes, isObjectEmpty } from "@utils/misc";
import { closeAllModals } from "@utils/modal";
import { relaunch } from "@utils/native";
import { Alerts, NavigationRouter, Toasts, useEffect, useState } from "@webpack/common";
import { Alerts, closeAllModals,NavigationRouter, Toasts, useEffect, useState } from "@webpack/common";
import userpluginInstaller, { Native } from "..";
import {

View file

@ -11,31 +11,22 @@ import { definePluginSettings } from "@api/Settings";
import { Button } from "@components/Button";
import { Divider } from "@components/Divider";
import ErrorBoundary from "@components/ErrorBoundary";
import { HeadingPrimary, HeadingSecondary } from "@components/Heading";
import { HeadingSecondary } from "@components/Heading";
import { Devs, EquicordDevs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { Margins } from "@utils/margins";
import {
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalProps,
ModalRoot,
ModalSize,
openModal,
} from "@utils/modal";
import { wordsToTitle } from "@utils/text";
import definePlugin, { OptionType } from "@utils/types";
import type { User } from "@vencord/discord-types";
import { RenderModalProps, type User } from "@vencord/discord-types";
import { ChannelType } from "@vencord/discord-types/enums";
import {
Button as DiscordButton,
ChannelStore,
Forms,
GuildMemberStore,
IconUtils,
Menu,
Modal,
openModal,
React,
SearchableSelect,
SelectedChannelStore,
@ -686,7 +677,7 @@ interface UserContextProps {
user: User;
}
function VoiceSelectModal({ modalProps, user }: { modalProps: ModalProps; user: User; }) {
function VoiceSelectModal({ modalProps, user }: { modalProps: RenderModalProps; user: User; }) {
const DEFAULT_VALUE = "__default__";
const options = useMemo(() => {
@ -741,80 +732,72 @@ function VoiceSelectModal({ modalProps, user }: { modalProps: ModalProps; user:
};
return (
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<ModalHeader>
<HeadingPrimary className={cl("modal-title")}>VC Narrator Voice</HeadingPrimary>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<Modal
{...modalProps}
size="md"
title="VC Narrator Voice"
actions={[
{
text: "Cancel",
variant: "secondary",
onClick: modalProps.onClose
},
{
text: "Save",
variant: "primary",
onClick: () => {
if (currentValue === DEFAULT_VALUE) {
settings.store.userVoiceMap = removeUserVoiceFromMap(settings.store.userVoiceMap, user.id);
} else {
settings.store.userVoiceMap = upsertUserVoiceMap(settings.store.userVoiceMap, user.id, currentValue);
}
modalProps.onClose();
}
}
]}
>
<section className={Margins.bottom16}>
<HeadingSecondary>Select voice for {user.username}</HeadingSecondary>
<SearchableSelect
options={options}
value={options.find(o => o.value === currentValue)?.value}
placeholder="Select a voice"
maxVisibleItems={6}
closeOnSelect={true}
onChange={v => setCurrentValue(v as any)}
/>
<ModalContent>
<section className={Margins.bottom16}>
<HeadingSecondary>Select voice for {user.username}</HeadingSecondary>
<SearchableSelect
options={options}
value={options.find(o => o.value === currentValue)?.value}
placeholder="Select a voice"
maxVisibleItems={6}
closeOnSelect={true}
onChange={v => setCurrentValue(v as any)}
/>
<Forms.FormText className={cl("preview-hint")}>
Preview how this voice sounds with their name:
</Forms.FormText>
<div className={cl("preview-buttons")}>
<Button
variant="secondary"
size="small"
disabled={busy}
onClick={() => previewVoice(clean(displayName, settings.store.latinOnly) || "Someone")}
>
{busy ? "Playing..." : `"${clean(displayName, settings.store.latinOnly) || "Someone"}"`}
</Button>
<Button
variant="secondary"
size="small"
disabled={busy}
onClick={() => previewVoice(`${clean(displayName, settings.store.latinOnly) || "Someone"} joined`)}
>
Joined
</Button>
<Button
variant="secondary"
size="small"
disabled={busy}
onClick={() => previewVoice(`${clean(displayName, settings.store.latinOnly) || "Someone"} left`)}
>
Left
</Button>
</div>
</section>
</ModalContent>
<ModalFooter>
<div className={cl("modal-footer")}>
<DiscordButton
color={DiscordButton.Colors.PRIMARY}
onClick={modalProps.onClose}
<Forms.FormText className={cl("preview-hint")}>
Preview how this voice sounds with their name:
</Forms.FormText>
<div className={cl("preview-buttons")}>
<Button
variant="secondary"
size="small"
disabled={busy}
onClick={() => previewVoice(clean(displayName, settings.store.latinOnly) || "Someone")}
>
Cancel
</DiscordButton>
<DiscordButton
color={DiscordButton.Colors.BRAND}
onClick={() => {
if (currentValue === DEFAULT_VALUE) {
settings.store.userVoiceMap = removeUserVoiceFromMap(settings.store.userVoiceMap, user.id);
} else {
settings.store.userVoiceMap = upsertUserVoiceMap(settings.store.userVoiceMap, user.id, currentValue);
}
modalProps.onClose();
}}
{busy ? "Playing..." : `"${clean(displayName, settings.store.latinOnly) || "Someone"}"`}
</Button>
<Button
variant="secondary"
size="small"
disabled={busy}
onClick={() => previewVoice(`${clean(displayName, settings.store.latinOnly) || "Someone"} joined`)}
>
Save
</DiscordButton>
Joined
</Button>
<Button
variant="secondary"
size="small"
disabled={busy}
onClick={() => previewVoice(`${clean(displayName, settings.store.latinOnly) || "Someone"} left`)}
>
Left
</Button>
</div>
</ModalFooter>
</ModalRoot>
</section>
</Modal>
);
}

View file

@ -5,7 +5,6 @@
*/
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { BaseText } from "@components/BaseText";
import { CodeBlock } from "@components/CodeBlock";
import { Divider } from "@components/Divider";
import ErrorBoundary from "@components/ErrorBoundary";
@ -13,18 +12,9 @@ import { Heading } from "@components/Heading";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { Margins } from "@utils/margins";
import {
closeModal,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalRoot,
ModalSize,
openModal,
} from "@utils/modal";
import definePlugin from "@utils/types";
import { Message } from "@vencord/discord-types";
import { Menu } from "@webpack/common";
import { Menu, Modal,openModal } from "@webpack/common";
type CustomMessage = Message & {
editHistory?: any;
@ -60,38 +50,34 @@ function cleanMessage(msg: CustomMessage) {
}
function openViewRawModal(obj: any, type: string, isMessage?: boolean) {
const key = openModal(props => (
openModal(props => (
<ErrorBoundary>
<ModalRoot {...props} size={ModalSize.LARGE}>
<ModalHeader>
<BaseText size="lg" weight="semibold" style={{ flexGrow: 1 }}>
View Raw {type}
</BaseText>
<ModalCloseButton onClick={() => closeModal(key)} />
</ModalHeader>
<ModalContent>
<div style={{ padding: "16px 0" }}>
{isMessage && (
<>
<Heading>
Content
</Heading>
<CodeBlock
content={obj.content}
lang="markdown"
/>
<Divider className={Margins.bottom20} />
</>
)}
<Modal
{...props}
size="lg"
title={`View Raw ${type}`}
>
<div style={{ padding: "16px 0" }}>
{isMessage && (
<>
<Heading>
Content
</Heading>
<CodeBlock
content={obj.content}
lang="markdown"
/>
<Divider className={Margins.bottom20} />
</>
)}
<Heading>{type} Data</Heading>
<CodeBlock
content={JSON.stringify(obj, null, 4)}
lang="json"
/>
</div>
</ModalContent>
</ModalRoot>
<Heading>{type} Data</Heading>
<CodeBlock
content={JSON.stringify(obj, null, 4)}
lang="json"
/>
</div>
</Modal>
</ErrorBoundary>
));
}

View file

@ -7,9 +7,8 @@
import "./VoiceChannelLogEntryComponent.css";
import { openUserProfile } from "@utils/discord";
import { closeAllModals } from "@utils/modal";
import { Channel } from "@vencord/discord-types";
import { ApplicationStore, Clickable, IconUtils, NavigationRouter, Timestamp, Tooltip, UserStore } from "@webpack/common";
import { ApplicationStore, Clickable, closeAllModals,IconUtils, NavigationRouter, Timestamp, Tooltip, UserStore } from "@webpack/common";
import { getCallStartTime } from "../logs";
import { VoiceChannelLogEntry } from "../types";

View file

@ -4,12 +4,10 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { BaseText } from "@components/BaseText";
import { classes } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Channel } from "@vencord/discord-types";
import { Channel, RenderModalProps } from "@vencord/discord-types";
import { findStoreLazy } from "@webpack";
import { Button, React, ScrollerThin } from "@webpack/common";
import { Modal, openModal, React, ScrollerThin } from "@webpack/common";
import { clearLogs, getVcLogs, vcLogSubscribe } from "../logs";
import { cl } from "../utils";
@ -23,47 +21,43 @@ export function openVoiceChannelLog(channel: Channel) {
));
}
export function VoiceChannelLogModal({ channel, props }: { channel: Channel; props: ModalProps; }) {
export function VoiceChannelLogModal({ channel, props }: { channel: Channel; props: RenderModalProps; }) {
const logs = React.useSyncExternalStore(vcLogSubscribe, () => getVcLogs(channel.id));
return (
<ModalRoot {...props} size={ModalSize.LARGE}>
<ModalHeader>
<BaseText size="lg" weight="semibold" className={cl("header")} style={{ flexGrow: 1 }}>
{channel.name} logs
</BaseText>
<ModalCloseButton onClick={props.onClose} />
</ModalHeader>
<ModalContent>
<ScrollerThin fade className={classes(cl("scroller"), `group-spacing-${AccessibilityStore.messageGroupSpacing}`)}>
{logs.length > 0 ? logs.map((entry, i) => {
const elements: React.ReactNode[] = [];
if (i === 0 || entry.timestamp.toDateString() !== logs[i - 1].timestamp.toDateString()) {
elements.push(
<div key={`sep-${i}`} className={cl("date-separator")} role="separator" aria-label={entry.timestamp.toDateString()}>
<span>{entry.timestamp.toDateString()}</span>
</div>
);
}
<Modal
{...props}
size="lg"
title={`${channel.name} logs`}
actions={[
{
text: "Clear logs",
variant: "dangerPrimary",
onClick: () => clearLogs(channel.id)
}
]}
>
<ScrollerThin fade className={classes(cl("scroller"), `group-spacing-${AccessibilityStore.messageGroupSpacing}`)}>
{logs.length > 0 ? logs.map((entry, i) => {
const elements: React.ReactNode[] = [];
if (i === 0 || entry.timestamp.toDateString() !== logs[i - 1].timestamp.toDateString()) {
elements.push(
<VoiceChannelLogEntryComponent key={`entry-${i}`} logEntry={entry} channel={channel} />
<div key={`sep-${i}`} className={cl("date-separator")} role="separator" aria-label={entry.timestamp.toDateString()}>
<span>{entry.timestamp.toDateString()}</span>
</div>
);
}
return elements;
}) : (
<div className={cl("empty")}>No logs to display.</div>
)}
</ScrollerThin>
</ModalContent>
elements.push(
<VoiceChannelLogEntryComponent key={`entry-${i}`} logEntry={entry} channel={channel} />
);
<ModalFooter>
<Button color={Button.Colors.RED} onClick={() => clearLogs(channel.id)}>
Clear logs
</Button>
</ModalFooter>
</ModalRoot>
return elements;
}) : (
<div className={cl("empty")}>No logs to display.</div>
)}
</ScrollerThin>
</Modal>
);
}

View file

@ -16,10 +16,10 @@ import { Heading } from "@components/Heading";
import { Span } from "@components/Span";
import { copyToClipboard } from "@utils/clipboard";
import { Devs } from "@utils/constants";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin, { OptionType, PluginNative } from "@utils/types";
import { RenderModalProps } from "@vencord/discord-types";
import { findComponentByCodeLazy } from "@webpack";
import { lodash, ScrollerAuto, SearchableSelect, useEffect, useRef, useState } from "@webpack/common";
import { lodash, Modal, openModal, ScrollerAuto, SearchableSelect, useEffect, useRef, useState } from "@webpack/common";
import { cl, decodeAudio, LANGUAGES, TranscriptionWorker } from "./utils";
const Native = VencordNative.pluginHelpers.VoiceMessageTranscriber as PluginNative<typeof import("./native")>;
@ -93,7 +93,7 @@ const settings = definePluginSettings({
}
});
function LanguageSelectionModal(props: { modalProps: ModalProps, src: string; }) {
function LanguageSelectionModal(props: { modalProps: RenderModalProps, src: string; }) {
const { modalProps, src } = props;
const [language, setLanguage] = useState<string>("auto");
const [task, setTask] = useState<string>("transcribe");
@ -118,51 +118,52 @@ function LanguageSelectionModal(props: { modalProps: ModalProps, src: string; })
};
return (
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<ModalHeader>
<BaseText size="lg" weight="bold" style={{ flexGrow: 1 }}>Transcription Options</BaseText>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent style={{ padding: "16px" }}>
<Flex flexDirection="column" gap={20}>
<div>
<BaseText size="sm" weight="semibold" style={{ marginBottom: "8px" }}>
Audio Language
</BaseText>
<SearchableSelect
options={languageOptions}
value={languageOptions.find(o => o.value === language)?.value}
onChange={setLanguage}
/>
</div>
<Modal
{...modalProps}
size="md"
title="Transcription Options"
actions={[
{
text: "Start",
variant: "primary",
onClick: start
}
]}
>
<Flex flexDirection="column" gap={20} style={{ padding: "16px" }}>
<div>
<BaseText size="sm" weight="semibold" style={{ marginBottom: "8px" }}>
Audio Language
</BaseText>
<SearchableSelect
options={languageOptions}
value={languageOptions.find(o => o.value === language)?.value}
onChange={setLanguage}
/>
</div>
<div>
<BaseText size="sm" weight="semibold" style={{ marginBottom: "8px" }}>
Action
</BaseText>
<ManaBaseRadioGroup
options={[{
name: "Transcribe",
value: "transcribe"
}, {
name: "Translate to English",
value: "translate"
}]}
value={task}
onChange={v => setTask(v as string)}
/>
</div>
<Button onClick={start} variant="primary">
Start
</Button>
</Flex>
</ModalContent>
</ModalRoot>
<div>
<BaseText size="sm" weight="semibold" style={{ marginBottom: "8px" }}>
Action
</BaseText>
<ManaBaseRadioGroup
options={[{
name: "Transcribe",
value: "transcribe"
}, {
name: "Translate to English",
value: "translate"
}]}
value={task}
onChange={v => setTask(v as string)}
/>
</div>
</Flex>
</Modal>
);
}
function TranscriptionModal(props: { modalProps: ModalProps, src: string, options: { language: string, task: string; }; }) {
function TranscriptionModal(props: { modalProps: RenderModalProps, src: string, options: { language: string, task: string; }; }) {
const { modalProps, src, options } = props;
const [status, setStatus] = useState<string>("initializing");
const [result, setResult] = useState<{ text: string, chunks: { timestamp: [number, number], text: string; }[]; } | null>(null);
@ -252,61 +253,71 @@ function TranscriptionModal(props: { modalProps: ModalProps, src: string, option
: result.text
) : "";
return <ModalRoot {...modalProps} size={ModalSize.LARGE}>
<ModalHeader>
<BaseText size="lg" weight="bold" style={{ flexGrow: 1 }}>Transcription</BaseText>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent className={cl("content")}>
{error ? (
<Flex flexDirection="column" gap={16} style={{ padding: "20px 0" }}>
<Heading tag="h3" style={{ color: "var(--red-360)" }}>Error</Heading>
<Span style={{ whiteSpace: "pre-wrap" }}>{error}</Span>
<Button onClick={retry}>
Retry
</Button>
</Flex>
) : (status === "complete" || (status === "transcribing" && result)) ? (
<Flex flexDirection="column" gap={16} style={{ paddingBottom: "20px" }}>
{status === "transcribing" && (
<Span size="sm" color="text-muted">Transcribing in progress...</Span>
)}
<ScrollerAuto className={cl("result")}>
<Span>{displayText}</Span>
</ScrollerAuto>
<Flex flexDirection="row" gap={12} alignItems="center">
<div style={{ flexGrow: 1 }}>
<FormSwitch
title="Show Timestamps"
value={showTimestamps}
onChange={setShowTimestamps}
/>
</div>
const actions: any[] = [];
if (error) {
actions.push({
text: "Retry",
variant: "primary",
onClick: retry
});
} else if (status === "complete" || (status === "transcribing" && result)) {
actions.push({
text: copied ? "Copied!" : "Copy to Clipboard",
variant: "primary",
disabled: status === "transcribing" || copied,
onClick: () => {
copyToClipboard(displayText);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
});
}
return (
<Modal
{...modalProps}
size="lg"
title="Transcription"
actions={actions}
>
<div className={cl("content")}>
{error ? (
<Flex flexDirection="column" gap={16} style={{ padding: "20px 0" }}>
<Heading tag="h3" style={{ color: "var(--red-360)" }}>Error</Heading>
<Span style={{ whiteSpace: "pre-wrap" }}>{error}</Span>
</Flex>
<Button
disabled={status === "transcribing" || copied}
onClick={() => {
copyToClipboard(displayText);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}}
>
{copied ? "Copied!" : "Copy to Clipboard"}
</Button>
</Flex>
) : (
<Flex flexDirection="column" gap={16} style={{ padding: "20px 0", alignItems: "center" }}>
<Heading tag="h3">
{status === "initializing" && "Initializing..."}
{status === "downloading_audio" && "Downloading Audio..."}
{status === "processing_audio" && "Processing Audio..."}
{status === "loading" && "Loading Model..."}
{status === "transcribing" && "Transcribing..."}
</Heading>
</Flex>
)}
</ModalContent>
</ModalRoot>;
) : (status === "complete" || (status === "transcribing" && result)) ? (
<Flex flexDirection="column" gap={16} style={{ paddingBottom: "20px" }}>
{status === "transcribing" && (
<Span size="sm" color="text-muted">Transcribing in progress...</Span>
)}
<ScrollerAuto className={cl("result")}>
<Span>{displayText}</Span>
</ScrollerAuto>
<Flex flexDirection="row" gap={12} alignItems="center">
<div style={{ flexGrow: 1 }}>
<FormSwitch
title="Show Timestamps"
value={showTimestamps}
onChange={setShowTimestamps}
/>
</div>
</Flex>
</Flex>
) : (
<Flex flexDirection="column" gap={16} style={{ padding: "20px 0", alignItems: "center" }}>
<Heading tag="h3">
{status === "initializing" && "Initializing..."}
{status === "downloading_audio" && "Downloading Audio..."}
{status === "processing_audio" && "Processing Audio..."}
{status === "loading" && "Loading Model..."}
{status === "transcribing" && "Transcribing..."}
</Heading>
</Flex>
)}
</div>
</Modal>
);
}
export default definePlugin({

View file

@ -8,13 +8,13 @@ import { definePluginSettings } from "@api/Settings";
import { BaseText } from "@components/BaseText";
import { Flex } from "@components/Flex";
import { FormSwitch } from "@components/FormSwitch";
import { Heading, HeadingPrimary } from "@components/Heading";
import { Heading } from "@components/Heading";
import { Devs } from "@utils/constants";
import { makeLazy } from "@utils/lazy";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { RenderModalProps } from "@vencord/discord-types";
import { findByProps, wreq } from "@webpack";
import { Button, Timestamp, useState } from "@webpack/common";
import { Button, Modal,openModal, Timestamp, useState } from "@webpack/common";
import TarFile from "./tar";
import * as Webpack from "./webpack";
@ -37,10 +37,9 @@ export default definePlugin({
toolboxActions: {
"Webpack Tarball"() {
const key = openModal(props => (
openModal(props => (
<TarModal
modalProps={props}
close={() => closeModal(key)}
/>
));
}
@ -83,7 +82,7 @@ async function saveTar(patched: boolean) {
tar.save(`${root}.tar`);
}
function TarModal({ modalProps, close }: { modalProps: ModalProps; close(): void; }) {
function TarModal({ modalProps }: { modalProps: RenderModalProps; }) {
const webpackRequire = wreq as any;
const { buildNumber, builtAt } = getBuildNumber();
const [, rerender] = useState({});
@ -99,66 +98,63 @@ function TarModal({ modalProps, close }: { modalProps: ModalProps; close(): void
const { patched } = settings.use(["patched"]);
const WEBPACK_CHUNK = "webpackChunkdiscord_app";
return (
<ModalRoot {...modalProps}>
<ModalHeader>
<HeadingPrimary>
Webpack Tarball
</HeadingPrimary>
<Modal
{...modalProps}
size="md"
title="Webpack Tarball"
actions={[
{
text: "Create",
variant: "primary",
onClick: () => {
saveTar(patched);
modalProps.onClose();
}
}
]}
>
<div style={{ marginBottom: "16px" }}>
<BaseText size="md">
<Timestamp timestamp={new Date(builtAt)} isInline={false}>
{"Build number "}
{buildNumber}
</Timestamp>
</BaseText>
<ModalCloseButton onClick={close} />
</ModalHeader>
</div>
<ModalContent>
<div style={{ marginTop: "8px", marginBottom: "24px" }}>
<Heading>
Lazy chunks
</Heading>
<Flex alignItems="center">
<BaseText
size="md"
style={{ flexGrow: 1 }}
>
{loaded}/{all}
{errored ? ` (${errored} errors)` : null}
</BaseText>
<Button
disabled={loading === all || isLoading}
onClick={async () => {
setLoading(true);
// @ts-ignore
await Webpack.protectWebpack(window[WEBPACK_CHUNK], async () => {
await Webpack.forceLoadAll(webpackRequire, rerender);
});
}}
>
{loaded === all ? "Loaded" : loading === all ? "Loading" : "Load all"}
</Button>
</Flex>
</div>
<div style={{ marginTop: "8px", marginBottom: "24px" }}>
<Heading>
Lazy chunks
</Heading>
<Flex alignItems="center">
<BaseText
size="md"
style={{ flexGrow: 1 }}
>
{loaded}/{all}
{errored ? ` (${errored} errors)` : null}
</BaseText>
<Button
disabled={loading === all || isLoading}
onClick={async () => {
setLoading(true);
// @ts-ignore
await Webpack.protectWebpack(window[WEBPACK_CHUNK], async () => {
await Webpack.forceLoadAll(webpackRequire, rerender);
});
}}
>
{loaded === all ? "Loaded" : loading === all ? "Loading" : "Load all"}
</Button>
</Flex>
</div>
<FormSwitch
value={patched}
onChange={v => settings.store.patched = v}
title={settings.def.patched.description}
hideBorder
/>
</ModalContent>
<ModalFooter>
<Button
onClick={() => {
saveTar(patched);
close();
}}
>
Create
</Button>
</ModalFooter>
</ModalRoot>
<FormSwitch
value={patched}
onChange={v => settings.store.patched = v}
title={settings.def.patched.description}
hideBorder
/>
</Modal>
);
}

View file

@ -4,16 +4,12 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Button } from "@components/Button";
import { CodeBlock } from "@components/CodeBlock";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Heading } from "@components/Heading";
import { ChevronSmallDownIcon, ChevronSmallUpIcon, FolderIcon } from "@components/Icons";
import { classNameFactory } from "@utils/css";
import { copyWithToast } from "@utils/discord";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useEffect, useMemo, useRef, useState } from "@webpack/common";
import { Modal,openModal, useEffect, useMemo, useRef, useState } from "@webpack/common";
import {
createImageObjectUrl,
@ -312,52 +308,50 @@ function openZipEntryModal(entry: ZipEntry) {
function openTextEntryModal(entry: ZipEntry) {
const content = readTextEntry(entry);
const key = openModal(modalProps => (
openModal(modalProps => (
<ErrorBoundary>
<ModalRoot {...modalProps} size={ModalSize.LARGE}>
<ModalHeader>
<Heading tag="h2" className={cl("modal-title")}>{entry.name}</Heading>
<ModalCloseButton onClick={() => closeModal(key)} />
</ModalHeader>
<ModalContent className={cl("modal-content")}>
<div className={cl("code-wrap")}>
<CodeBlock content={content} lang={getCodeLanguage(entry)} />
</div>
</ModalContent>
<ModalFooter>
<Flex gap={8} justifyContent="flex-end">
<Button variant="secondary" onClick={() => copyWithToast(content, "File contents copied to clipboard!")}>
Copy
</Button>
<Button onClick={() => makeDownload(entry)}>
Download
</Button>
</Flex>
</ModalFooter>
</ModalRoot>
<Modal
{...modalProps}
size="lg"
title={entry.name}
actions={[
{
text: "Copy",
variant: "secondary",
onClick: () => copyWithToast(content, "File contents copied to clipboard!")
},
{
text: "Download",
variant: "primary",
onClick: () => makeDownload(entry)
}
]}
>
<div className={cl("code-wrap")}>
<CodeBlock content={content} lang={getCodeLanguage(entry)} />
</div>
</Modal>
</ErrorBoundary>
));
}
function openImageEntryModal(entry: ZipEntry) {
const key = openModal(modalProps => (
openModal(modalProps => (
<ErrorBoundary>
<ModalRoot {...modalProps} size={ModalSize.LARGE}>
<ModalHeader>
<Heading tag="h2" className={cl("modal-title")}>{entry.name}</Heading>
<ModalCloseButton onClick={() => closeModal(key)} />
</ModalHeader>
<ModalContent className={cl("modal-content")}>
<ZipImagePreview entry={entry} />
</ModalContent>
<ModalFooter>
<Flex gap={8} justifyContent="flex-end">
<Button onClick={() => makeDownload(entry)}>
Download
</Button>
</Flex>
</ModalFooter>
</ModalRoot>
<Modal
{...modalProps}
size="lg"
title={entry.name}
actions={[
{
text: "Download",
variant: "primary",
onClick: () => makeDownload(entry)
}
]}
>
<ZipImagePreview entry={entry} />
</Modal>
</ErrorBoundary>
));
}

View file

@ -4,35 +4,29 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Button } from "@components/Button";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize } from "@utils/modal";
import { Modal } from "@webpack/common";
import { ClearAliasesConfirmModalProps } from "./types";
export function ClearAliasesConfirmModal({ modalProps, onConfirm }: ClearAliasesConfirmModalProps) {
return (
<ModalRoot {...modalProps} size={ModalSize.SMALL}>
<ModalHeader>
<Heading style={{ flexGrow: 1 }}>Delete all aliases</Heading>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent>
<Paragraph>This will remove every emoji alias you saved.</Paragraph>
</ModalContent>
<ModalFooter>
<Button
variant="dangerPrimary"
style={{ marginLeft: "auto" }}
onClick={async () => {
<Modal
{...modalProps}
size="sm"
title="Delete all aliases"
actions={[
{
text: "Delete all aliases",
variant: "danger-primary",
onClick: async () => {
await onConfirm();
modalProps.onClose();
}}
>
Delete all aliases
</Button>
</ModalFooter>
</ModalRoot>
}
}
]}
>
<Paragraph>This will remove every emoji alias you saved.</Paragraph>
</Modal>
);
}

View file

@ -5,11 +5,8 @@
*/
import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize } from "@utils/modal";
import { TextInput, useState } from "@webpack/common";
import { Modal, TextInput, useState } from "@webpack/common";
import { SetAliasModalProps } from "./types";
@ -30,45 +27,40 @@ export function SetAliasModal({
const canSave = !validationError && !duplicateAlias;
return (
<ModalRoot {...modalProps} size={ModalSize.SMALL}>
<ModalHeader>
<Heading style={{ flexGrow: 1 }}>Set alias</Heading>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent style={{ overflowY: "hidden" }}>
<Paragraph style={{ margin: 0, marginBottom: 8 }}>Set an alias for {emojiDisplayName}</Paragraph>
<TextInput
value={input}
onChange={value => {
setInput(value);
setError(null);
}}
placeholder='Alias, e.g. "sob"'
/>
{finalError && (
<BaseText style={{ color: "var(--text-feedback-critical)", marginTop: 8 }}>
{finalError}
</BaseText>
)}
</ModalContent>
<ModalFooter>
<Button
variant="primary"
disabled={!canSave}
onClick={async () => {
<Modal
{...modalProps}
size="sm"
title="Set alias"
actions={[
{
text: "Save",
variant: "primary",
disabled: !canSave,
onClick: async () => {
const result = await onSave(input);
if (!result.ok) {
setError(result.error);
return;
}
modalProps.onClose();
}}
>
Save
</Button>
</ModalFooter>
</ModalRoot>
}
}
]}
>
<Paragraph style={{ margin: 0, marginBottom: 8 }}>Set an alias for {emojiDisplayName}</Paragraph>
<TextInput
value={input}
onChange={value => {
setInput(value);
setError(null);
}}
placeholder='Alias, e.g. "sob"'
/>
{finalError && (
<BaseText style={{ color: "var(--text-feedback-critical)", marginTop: 8 }}>
{finalError}
</BaseText>
)}
</Modal>
);
}

View file

@ -4,12 +4,12 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ModalProps } from "@utils/modal";
import { RenderModalProps } from "@vencord/discord-types";
export type SetAliasSaveResult = { ok: true; } | { ok: false; error: string; };
export interface SetAliasModalProps {
modalProps: ModalProps;
modalProps: RenderModalProps;
emojiDisplayName: string;
initialAlias: string;
getValidationError: (input: string) => string | null;
@ -18,6 +18,6 @@ export interface SetAliasModalProps {
}
export interface ClearAliasesConfirmModalProps {
modalProps: ModalProps;
modalProps: RenderModalProps;
onConfirm: () => Promise<void>;
}

View file

@ -16,11 +16,10 @@ import { Paragraph } from "@components/Paragraph";
import { Devs, EquicordDevs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { Logger } from "@utils/Logger";
import { openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { Emoji, Message } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack";
import { EmojiStore, Menu, TextInput, Toasts, useEffect, useState } from "@webpack/common";
import { EmojiStore, Menu, openModal,TextInput, Toasts, useEffect, useState } from "@webpack/common";
import { ClearAliasesConfirmModal } from "./components/modals/ClearAliasesConfirmModal";
import { SetAliasModal } from "./components/modals/SetAliasModal";

View file

@ -51,22 +51,26 @@ function getEmojiValue(emoji: EmojiSelectPayload | null | undefined) {
return emoji.name?.trim() ?? "";
}
function toRenderedEmoji(value: string) {
type RenderedEmoji =
| { kind: "custom"; id: string; name: string; animated: boolean; }
| { kind: "unicode"; name: string; animated: boolean; };
function toRenderedEmoji(value: string): RenderedEmoji | null {
const trimmed = value.trim();
if (!trimmed) return null;
const customEmoji = parseCustomEmoji(trimmed);
if (customEmoji) {
return {
kind: "custom" as const,
id: customEmoji[3],
name: customEmoji[2],
kind: "custom",
id: customEmoji[3]!,
name: customEmoji[2]!,
animated: customEmoji[1] === "a"
};
}
return {
kind: "unicode" as const,
kind: "unicode",
name: trimmed,
animated: false
};

View file

@ -7,7 +7,6 @@
import "./styles.css";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { classNameFactory } from "@utils/css";
import { getGuildAcronym, openImageModal, openUserProfile } from "@utils/discord";
import { classes } from "@utils/misc";

View file

@ -10,17 +10,17 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co
import { DataStore } from "@api/index";
import { isPluginEnabled } from "@api/PluginManager";
import { definePluginSettings } from "@api/Settings";
import { Button, TextButton } from "@components/Button";
import { TextButton } from "@components/Button";
import ErrorBoundary from "@components/ErrorBoundary";
import { Heading } from "@components/Heading";
import ircColors from "@plugins/ircColors";
import mentionAvatars from "@plugins/mentionAvatars";
import { Devs, EquicordDevs } from "@utils/constants";
import { classNameFactory, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/index";
import { classNameFactory } from "@utils/index";
import definePlugin, { OptionType } from "@utils/types";
import { GuildMember, Message, User } from "@vencord/discord-types";
import { GuildMember, Message, RenderModalProps, User } from "@vencord/discord-types";
import { findByCodeLazy, findStoreLazy } from "@webpack";
import { ChannelStore, GuildMemberStore, GuildStore, Menu, MessageStore, RelationshipStore, StreamerModeStore, TextInput, useEffect, useState } from "@webpack/common";
import { ChannelStore, GuildMemberStore, GuildStore, Menu, MessageStore, Modal, openModal, RelationshipStore, StreamerModeStore, TextInput, useEffect, useState } from "@webpack/common";
import { JSX } from "react";
const SMYNC = classNameFactory();
@ -842,49 +842,19 @@ function removeHoveringReactionPopout(id: string) {
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
}
function CustomNicknameModal({ modalProps, user }: { modalProps: ModalProps; user: User; }) {
function CustomNicknameModal({ modalProps, user }: { modalProps: RenderModalProps; user: User; }) {
const [value, setValue] = useState(customNicknames[user.id] ?? "");
return (
<ModalRoot {...modalProps}>
<ModalHeader>
<Heading tag="h1" style={{ flexGrow: 1, margin: 0 }}>
{customNicknames[user.id] ? "Change SMYN Nickname" : "Add SMYN Nickname"}
</Heading>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent>
<Heading tag="h3" style={{ marginBottom: 8, fontSize: "16px", fontWeight: "400", lineHeight: "1.25", color: "var(--text-subtle)" }}>
{"Set a custom SMYN nickname for this user. Make use of it by specifying {custom} in the SMYN template settings."}
</Heading>
<div style={{ paddingTop: "10px", flexGrow: 0 }}></div>
<Heading tag="h3" style={{ marginBottom: 8, fontSize: "14px", fontWeight: 600 }}>
SMYN Nickname
</Heading>
<TextInput
value={value}
maxLength={32}
onChange={setValue}
placeholder={user.globalName ?? user.username}
style={{ width: "100%" }}
/>
<TextButton
className="smyn-reset-button"
onClick={async () => {
setValue("");
delete customNicknames[user.id];
await DataStore.set("SMYNCustomNicknames", customNicknames);
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
}}
>
Reset SMYN Nickname
</TextButton>
<div style={{ paddingTop: "10px", flexGrow: 0 }}></div>
</ModalContent>
<ModalFooter className="smyn-modal-footer-container">
<Button
variant="primary"
onClick={async () => {
<Modal
{...modalProps}
size="sm"
title={customNicknames[user.id] ? "Change SMYN Nickname" : "Add SMYN Nickname"}
actions={[
{
text: "Save",
variant: "primary",
onClick: async () => {
const trimmed = value.trim().slice(0, 32).trim();
if (trimmed) {
@ -896,19 +866,42 @@ function CustomNicknameModal({ modalProps, user }: { modalProps: ModalProps; use
await DataStore.set("SMYNCustomNicknames", customNicknames);
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
modalProps.onClose();
}}
>
Save
</Button>
<Button
variant="secondary"
style={{ marginRight: "8px" }}
onClick={modalProps.onClose}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>
}
},
{
text: "Cancel",
variant: "secondary",
onClick: modalProps.onClose
}
]}
>
<Heading tag="h3" style={{ marginBottom: 8, fontSize: "16px", fontWeight: "400", lineHeight: "1.25", color: "var(--text-subtle)" }}>
{"Set a custom SMYN nickname for this user. Make use of it by specifying {custom} in the SMYN template settings."}
</Heading>
<div style={{ paddingTop: "10px", flexGrow: 0 }}></div>
<Heading tag="h3" style={{ marginBottom: 8, fontSize: "14px", fontWeight: 600 }}>
SMYN Nickname
</Heading>
<TextInput
value={value}
maxLength={32}
onChange={setValue}
placeholder={user.globalName ?? user.username}
style={{ width: "100%" }}
/>
<TextButton
className="smyn-reset-button"
onClick={async () => {
setValue("");
delete customNicknames[user.id];
await DataStore.set("SMYNCustomNicknames", customNicknames);
settings.store.triggerNameRerender = !settings.store.triggerNameRerender;
}}
>
Reset SMYN Nickname
</TextButton>
<div style={{ paddingTop: "10px", flexGrow: 0 }}></div>
</Modal>
);
}

View file

@ -17,7 +17,6 @@
*/
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
import { Paragraph } from "@components/Paragraph";
import { TooltipContainer } from "@components/TooltipContainer";
import { classes } from "@utils/misc";
import { IconComponent } from "@utils/types";

View file

@ -21,7 +21,7 @@ import { FormSwitch } from "@components/FormSwitch";
import { HeadingSecondary } from "@components/Heading";
import { Margins } from "@utils/margins";
import { RenderModalProps } from "@vencord/discord-types";
import { Forms, Modal, openModal, SearchableSelect, useMemo } from "@webpack/common";
import { Modal, openModal, SearchableSelect, useMemo } from "@webpack/common";
import { settings } from "./settings";
import { getLanguages } from "./utils";

View file

@ -18,7 +18,6 @@
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { BaseText } from "@components/BaseText";
import { CodeBlock } from "@components/CodeBlock";
import { Divider } from "@components/Divider";
import ErrorBoundary from "@components/ErrorBoundary";
@ -30,7 +29,6 @@ import definePlugin, { IconComponent, OptionType } from "@utils/types";
import { Message } from "@vencord/discord-types";
import { ChannelStore, Forms, GuildRoleStore, Menu, Modal, openModal } from "@webpack/common";
const CopyIcon: IconComponent = ({ height = 20, width = 20, className }) => {
return (
<svg