improve settings ui
This commit is contained in:
parent
8a7c0d7e61
commit
5c05443f45
|
@ -19,6 +19,8 @@
|
||||||
import * as DataStore from "@api/DataStore";
|
import * as DataStore from "@api/DataStore";
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
|
import { openNotificationSettingsModal } from "@components/VencordSettings/NotificationSettings";
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
||||||
|
@ -170,24 +172,31 @@ function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Flex>
|
||||||
disabled={log.length === 0}
|
<Button onClick={openNotificationSettingsModal}>
|
||||||
onClick={() => {
|
Notification Settings
|
||||||
Alerts.show({
|
</Button>
|
||||||
title: "Are you sure?",
|
|
||||||
body: `This will permanently remove ${log.length} notification${log.length === 1 ? "" : "s"}. This action cannot be undone.`,
|
<Button
|
||||||
async onConfirm() {
|
disabled={log.length === 0}
|
||||||
await DataStore.set(KEY, []);
|
color={Button.Colors.RED}
|
||||||
signals.forEach(x => x());
|
onClick={() => {
|
||||||
},
|
Alerts.show({
|
||||||
confirmText: "Do it!",
|
title: "Are you sure?",
|
||||||
confirmColor: "vc-notification-log-danger-btn",
|
body: `This will permanently remove ${log.length} notification${log.length === 1 ? "" : "s"}. This action cannot be undone.`,
|
||||||
cancelText: "Nevermind"
|
async onConfirm() {
|
||||||
});
|
await DataStore.set(KEY, []);
|
||||||
}}
|
signals.forEach(x => x());
|
||||||
>
|
},
|
||||||
Clear Notification Log
|
confirmText: "Do it!",
|
||||||
</Button>
|
confirmColor: "vc-notification-log-danger-btn",
|
||||||
|
cancelText: "Nevermind"
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear Notification Log
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalRoot>
|
</ModalRoot>
|
||||||
);
|
);
|
||||||
|
|
28
src/components/Grid.tsx
Normal file
28
src/components/Grid.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { CSSProperties } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
columns: number;
|
||||||
|
gap?: string;
|
||||||
|
inline?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Grid(props: Props & JSX.IntrinsicElements["div"]) {
|
||||||
|
const style: CSSProperties = {
|
||||||
|
display: props.inline ? "inline-grid" : "grid",
|
||||||
|
gridTemplateColumns: `repeat(${props.columns}, 1fr)`,
|
||||||
|
gap: props.gap,
|
||||||
|
...props.style
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...props} style={style}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -18,19 +18,17 @@
|
||||||
|
|
||||||
import "./iconStyles.css";
|
import "./iconStyles.css";
|
||||||
|
|
||||||
|
import { getTheme, Theme } from "@utils/discord";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { i18n } from "@webpack/common";
|
import { i18n } from "@webpack/common";
|
||||||
import type { PropsWithChildren, SVGProps } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
|
|
||||||
interface BaseIconProps extends IconProps {
|
interface BaseIconProps extends IconProps {
|
||||||
viewBox: string;
|
viewBox: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IconProps extends SVGProps<SVGSVGElement> {
|
type IconProps = JSX.IntrinsicElements["svg"];
|
||||||
className?: string;
|
type ImageProps = JSX.IntrinsicElements["img"];
|
||||||
height?: string | number;
|
|
||||||
width?: string | number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
|
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
|
||||||
return (
|
return (
|
||||||
|
@ -329,3 +327,88 @@ export function NotesIcon(props: IconProps) {
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function FolderIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-folder-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M2 5a3 3 0 0 1 3-3h3.93a2 2 0 0 1 1.66.9L12 5h7a3 3 0 0 1 3 3v11a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LogIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-log-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M3.11 8H6v10.82c0 .86.37 1.68 1 2.27.46.43 1.02.71 1.63.84A1 1 0 0 0 9 22h10a4 4 0 0 0 4-4v-1a2 2 0 0 0-2-2h-1V5a3 3 0 0 0-3-3H4.67c-.87 0-1.7.32-2.34.9-.63.6-1 1.42-1 2.28 0 .71.3 1.35.52 1.75a5.35 5.35 0 0 0 .48.7l.01.01h.01L3.11 7l-.76.65a1 1 0 0 0 .76.35Zm1.56-4c-.38 0-.72.14-.97.37-.24.23-.37.52-.37.81a1.69 1.69 0 0 0 .3.82H6v-.83c0-.29-.13-.58-.37-.8C5.4 4.14 5.04 4 4.67 4Zm5 13a3.58 3.58 0 0 1 0 3H19a2 2 0 0 0 2-2v-1H9.66ZM3.86 6.35ZM11 8a1 1 0 1 0 0 2h5a1 1 0 1 0 0-2h-5Zm-1 5a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2h-5a1 1 0 0 1-1-1Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RestartIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-restart-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 0 1 14.93-4H15a1 1 0 1 0 0 2h6a1 1 0 0 0 1-1V3a1 1 0 1 0-2 0v3a9.98 9.98 0 0 0-18 6 10 10 0 0 0 16.29 7.78 1 1 0 0 0-1.26-1.56A8 8 0 0 1 4 12Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PaintbrushIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-paintbrush-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M15.35 7.24C15.9 6.67 16 5.8 16 5a3 3 0 1 1 3 3c-.8 0-1.67.09-2.24.65a1.5 1.5 0 0 0 0 2.11l1.12 1.12a3 3 0 0 1 0 4.24l-5 5a3 3 0 0 1-4.25 0l-5.76-5.75a3 3 0 0 1 0-4.24l4.04-4.04.97-.97a3 3 0 0 1 4.24 0l1.12 1.12c.58.58 1.52.58 2.1 0ZM6.9 9.9 4.3 12.54a1 1 0 0 0 0 1.42l2.17 2.17.83-.84a1 1 0 0 1 1.42 1.42l-.84.83.59.59 1.83-1.84a1 1 0 0 1 1.42 1.42l-1.84 1.83.17.17a1 1 0 0 0 1.42 0l2.63-2.62L6.9 9.9Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
||||||
|
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
||||||
|
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
||||||
|
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
||||||
|
|
||||||
|
export function GithubIcon(props: ImageProps) {
|
||||||
|
const src = getTheme() === Theme.Light
|
||||||
|
? GithubIconLight
|
||||||
|
: GithubIconDark;
|
||||||
|
|
||||||
|
return <img {...props} src={src} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WebsiteIcon(props: ImageProps) {
|
||||||
|
const src = getTheme() === Theme.Light
|
||||||
|
? WebsiteIconLight
|
||||||
|
: WebsiteIconDark;
|
||||||
|
|
||||||
|
return <img {...props} src={src} />;
|
||||||
|
}
|
||||||
|
|
|
@ -6,22 +6,16 @@
|
||||||
|
|
||||||
import "./LinkIconButton.css";
|
import "./LinkIconButton.css";
|
||||||
|
|
||||||
import { getTheme, Theme } from "@utils/discord";
|
|
||||||
import { MaskedLink, Tooltip } from "@webpack/common";
|
import { MaskedLink, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
import { GithubIcon, WebsiteIcon } from "..";
|
||||||
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
|
||||||
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
|
||||||
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
|
||||||
|
|
||||||
export function GithubIcon() {
|
export function GithubLinkIcon() {
|
||||||
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
|
return <GithubIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
||||||
return <img src={src} aria-hidden className={"vc-settings-modal-link-icon"} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WebsiteIcon() {
|
export function WebsiteLinkIcon() {
|
||||||
const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
|
return <WebsiteIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
||||||
return <img src={src} aria-hidden className={"vc-settings-modal-link-icon"} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -41,5 +35,5 @@ function LinkIcon({ text, href, Icon }: Props & { Icon: React.ComponentType; })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WebsiteButton = (props: Props) => <LinkIcon {...props} Icon={WebsiteIcon} />;
|
export const WebsiteButton = (props: Props) => <LinkIcon {...props} Icon={WebsiteLinkIcon} />;
|
||||||
export const GithubButton = (props: Props) => <LinkIcon {...props} Icon={GithubIcon} />;
|
export const GithubButton = (props: Props) => <LinkIcon {...props} Icon={GithubLinkIcon} />;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { gitRemote } from "@shared/vencordUserAgent";
|
||||||
import { proxyLazy } from "@utils/lazy";
|
import { proxyLazy } from "@utils/lazy";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes, isObjectEmpty } from "@utils/misc";
|
import { classes, isObjectEmpty } from "@utils/misc";
|
||||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
|
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { OptionType, Plugin } from "@utils/types";
|
import { OptionType, Plugin } from "@utils/types";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
||||||
|
@ -310,3 +310,13 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
</ModalRoot>
|
</ModalRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function openPluginModal(plugin: Plugin, onRestartNeeded?: (pluginName: string) => void) {
|
||||||
|
openModal(modalProps => (
|
||||||
|
<PluginModal
|
||||||
|
{...modalProps}
|
||||||
|
plugin={plugin}
|
||||||
|
onRestartNeeded={() => onRestartNeeded?.(plugin.name)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { showNotice } from "@api/Notices";
|
||||||
import { Settings, useSettings } from "@api/Settings";
|
import { Settings, useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { CogWheel, InfoIcon } from "@components/Icons";
|
import { CogWheel, InfoIcon } from "@components/Icons";
|
||||||
import PluginModal from "@components/PluginSettings/PluginModal";
|
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||||
import { AddonCard } from "@components/VencordSettings/AddonCard";
|
import { AddonCard } from "@components/VencordSettings/AddonCard";
|
||||||
import { SettingsTab } from "@components/VencordSettings/shared";
|
import { SettingsTab } from "@components/VencordSettings/shared";
|
||||||
import { ChangeList } from "@utils/ChangeList";
|
import { ChangeList } from "@utils/ChangeList";
|
||||||
|
@ -31,7 +31,6 @@ import { proxyLazy } from "@utils/lazy";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes, isObjectEmpty } from "@utils/misc";
|
import { classes, isObjectEmpty } from "@utils/misc";
|
||||||
import { openModalLazy } from "@utils/modal";
|
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Plugin } from "@utils/types";
|
import { Plugin } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
@ -45,7 +44,7 @@ const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() =>
|
||||||
const cl = classNameFactory("vc-plugins-");
|
const cl = classNameFactory("vc-plugins-");
|
||||||
const logger = new Logger("PluginSettings", "#a6d189");
|
const logger = new Logger("PluginSettings", "#a6d189");
|
||||||
|
|
||||||
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper");
|
const InputStyles = findByPropsLazy("inputWrapper", "inputDefault", "error");
|
||||||
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
|
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,14 +95,6 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
||||||
|
|
||||||
const isEnabled = () => settings.enabled ?? false;
|
const isEnabled = () => settings.enabled ?? false;
|
||||||
|
|
||||||
function openModal() {
|
|
||||||
openModalLazy(async () => {
|
|
||||||
return modalProps => {
|
|
||||||
return <PluginModal {...modalProps} plugin={plugin} onRestartNeeded={() => onRestartNeeded(plugin.name)} />;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleEnabled() {
|
function toggleEnabled() {
|
||||||
const wasEnabled = isEnabled();
|
const wasEnabled = isEnabled();
|
||||||
|
|
||||||
|
@ -160,7 +151,11 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
infoButton={
|
infoButton={
|
||||||
<button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}>
|
<button
|
||||||
|
role="switch"
|
||||||
|
onClick={() => openPluginModal(plugin, onRestartNeeded)}
|
||||||
|
className={classes(ButtonClasses.button, cl("info-button"))}
|
||||||
|
>
|
||||||
{plugin.options && !isObjectEmpty(plugin.options)
|
{plugin.options && !isObjectEmpty(plugin.options)
|
||||||
? <CogWheel />
|
? <CogWheel />
|
||||||
: <InfoIcon />}
|
: <InfoIcon />}
|
||||||
|
@ -339,8 +334,8 @@ export default function PluginSettings() {
|
||||||
Filters
|
Filters
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
|
|
||||||
<div className={cl("filter-controls")}>
|
<div className={classes(Margins.bottom20, cl("filter-controls"))}>
|
||||||
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} className={Margins.bottom20} />
|
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} />
|
||||||
<div className={InputStyles.inputWrapper}>
|
<div className={InputStyles.inputWrapper}>
|
||||||
<Select
|
<Select
|
||||||
options={[
|
options={[
|
||||||
|
@ -353,6 +348,7 @@ export default function PluginSettings() {
|
||||||
select={onStatusChange}
|
select={onStatusChange}
|
||||||
isSelected={v => v === searchValue.status}
|
isSelected={v => v === searchValue.status}
|
||||||
closeOnSelect={true}
|
closeOnSelect={true}
|
||||||
|
className={InputStyles.inputDefault}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
import { showNotification } from "@api/Notifications";
|
import { showNotification } from "@api/Notifications";
|
||||||
import { Settings, useSettings } from "@api/Settings";
|
import { Settings, useSettings } from "@api/Settings";
|
||||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||||
|
import { Grid } from "@components/Grid";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
|
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
|
@ -85,7 +86,9 @@ function SettingsSyncSection() {
|
||||||
size={Button.Sizes.SMALL}
|
size={Button.Sizes.SMALL}
|
||||||
disabled={!sectionEnabled}
|
disabled={!sectionEnabled}
|
||||||
onClick={() => putCloudSettings(true)}
|
onClick={() => putCloudSettings(true)}
|
||||||
>Sync to Cloud</Button>
|
>
|
||||||
|
Sync to Cloud
|
||||||
|
</Button>
|
||||||
<Tooltip text="This will overwrite your local settings with the ones on the cloud. Use wisely!">
|
<Tooltip text="This will overwrite your local settings with the ones on the cloud. Use wisely!">
|
||||||
{({ onMouseLeave, onMouseEnter }) => (
|
{({ onMouseLeave, onMouseEnter }) => (
|
||||||
<Button
|
<Button
|
||||||
|
@ -95,7 +98,9 @@ function SettingsSyncSection() {
|
||||||
color={Button.Colors.RED}
|
color={Button.Colors.RED}
|
||||||
disabled={!sectionEnabled}
|
disabled={!sectionEnabled}
|
||||||
onClick={() => getCloudSettings(true, true)}
|
onClick={() => getCloudSettings(true, true)}
|
||||||
>Sync from Cloud</Button>
|
>
|
||||||
|
Sync from Cloud
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Button
|
<Button
|
||||||
|
@ -103,7 +108,9 @@ function SettingsSyncSection() {
|
||||||
color={Button.Colors.RED}
|
color={Button.Colors.RED}
|
||||||
disabled={!sectionEnabled}
|
disabled={!sectionEnabled}
|
||||||
onClick={() => deleteCloudSettings()}
|
onClick={() => deleteCloudSettings()}
|
||||||
>Delete Cloud Settings</Button>
|
>
|
||||||
|
Delete Cloud Settings
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
);
|
);
|
||||||
|
@ -124,7 +131,12 @@ function CloudTab() {
|
||||||
<Switch
|
<Switch
|
||||||
key="backend"
|
key="backend"
|
||||||
value={settings.cloud.authenticated}
|
value={settings.cloud.authenticated}
|
||||||
onChange={v => { v && authorizeCloud(); if (!v) settings.cloud.authenticated = v; }}
|
onChange={v => {
|
||||||
|
if (v)
|
||||||
|
authorizeCloud();
|
||||||
|
else
|
||||||
|
settings.cloud.authenticated = v;
|
||||||
|
}}
|
||||||
note="This will request authorization if you have not yet set up cloud integrations."
|
note="This will request authorization if you have not yet set up cloud integrations."
|
||||||
>
|
>
|
||||||
Enable Cloud Integrations
|
Enable Cloud Integrations
|
||||||
|
@ -136,23 +148,43 @@ function CloudTab() {
|
||||||
<CheckedTextInput
|
<CheckedTextInput
|
||||||
key="backendUrl"
|
key="backendUrl"
|
||||||
value={settings.cloud.url}
|
value={settings.cloud.url}
|
||||||
onChange={v => { settings.cloud.url = v; settings.cloud.authenticated = false; deauthorizeCloud(); }}
|
onChange={async v => {
|
||||||
|
settings.cloud.url = v;
|
||||||
|
settings.cloud.authenticated = false;
|
||||||
|
deauthorizeCloud();
|
||||||
|
}}
|
||||||
validate={validateUrl}
|
validate={validateUrl}
|
||||||
/>
|
/>
|
||||||
<Button
|
|
||||||
className={Margins.top8}
|
<Grid columns={2} gap="1em" className={Margins.top8}>
|
||||||
size={Button.Sizes.MEDIUM}
|
<Button
|
||||||
color={Button.Colors.RED}
|
size={Button.Sizes.MEDIUM}
|
||||||
disabled={!settings.cloud.authenticated}
|
disabled={!settings.cloud.authenticated}
|
||||||
onClick={() => Alerts.show({
|
onClick={async () => {
|
||||||
title: "Are you sure?",
|
await deauthorizeCloud();
|
||||||
body: "Once your data is erased, we cannot recover it. There's no going back!",
|
settings.cloud.authenticated = false;
|
||||||
onConfirm: eraseAllData,
|
await authorizeCloud();
|
||||||
confirmText: "Erase it!",
|
}}
|
||||||
confirmColor: "vc-cloud-erase-data-danger-btn",
|
>
|
||||||
cancelText: "Nevermind"
|
Reauthorise
|
||||||
})}
|
</Button>
|
||||||
>Erase All Data</Button>
|
<Button
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
color={Button.Colors.RED}
|
||||||
|
disabled={!settings.cloud.authenticated}
|
||||||
|
onClick={() => Alerts.show({
|
||||||
|
title: "Are you sure?",
|
||||||
|
body: "Once your data is erased, we cannot recover it. There's no going back!",
|
||||||
|
onConfirm: eraseAllData,
|
||||||
|
confirmText: "Erase it!",
|
||||||
|
confirmColor: "vc-cloud-erase-data-danger-btn",
|
||||||
|
cancelText: "Nevermind"
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Erase All Data
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Forms.FormDivider className={Margins.top16} />
|
<Forms.FormDivider className={Margins.top16} />
|
||||||
</Forms.FormSection >
|
</Forms.FormSection >
|
||||||
<SettingsSyncSection />
|
<SettingsSyncSection />
|
||||||
|
|
106
src/components/VencordSettings/NotificationSettings.tsx
Normal file
106
src/components/VencordSettings/NotificationSettings.tsx
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useSettings } from "@api/Settings";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
|
import { identity } from "@utils/misc";
|
||||||
|
import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
|
import { Forms, Select, Slider, Text } from "@webpack/common";
|
||||||
|
|
||||||
|
import { ErrorCard } from "..";
|
||||||
|
|
||||||
|
export function NotificationSettings() {
|
||||||
|
const settings = useSettings().notifications;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "1em 0" }}>
|
||||||
|
<Forms.FormTitle tag="h5">Notification Style</Forms.FormTitle>
|
||||||
|
{settings.useNative !== "never" && Notification?.permission === "denied" && (
|
||||||
|
<ErrorCard style={{ padding: "1em" }} className={Margins.bottom8}>
|
||||||
|
<Forms.FormTitle tag="h5">Desktop Notification Permission denied</Forms.FormTitle>
|
||||||
|
<Forms.FormText>You have denied Notification Permissions. Thus, Desktop notifications will not work!</Forms.FormText>
|
||||||
|
</ErrorCard>
|
||||||
|
)}
|
||||||
|
<Forms.FormText className={Margins.bottom8}>
|
||||||
|
Some plugins may show you notifications. These come in two styles:
|
||||||
|
<ul>
|
||||||
|
<li><strong>Vencord Notifications</strong>: These are in-app notifications</li>
|
||||||
|
<li><strong>Desktop Notifications</strong>: Native Desktop notifications (like when you get a ping)</li>
|
||||||
|
</ul>
|
||||||
|
</Forms.FormText>
|
||||||
|
<Select
|
||||||
|
placeholder="Notification Style"
|
||||||
|
options={[
|
||||||
|
{ label: "Only use Desktop notifications when Discord is not focused", value: "not-focused", default: true },
|
||||||
|
{ label: "Always use Desktop notifications", value: "always" },
|
||||||
|
{ label: "Always use Vencord notifications", value: "never" },
|
||||||
|
] satisfies Array<{ value: typeof settings["useNative"]; } & Record<string, any>>}
|
||||||
|
closeOnSelect={true}
|
||||||
|
select={v => settings.useNative = v}
|
||||||
|
isSelected={v => v === settings.useNative}
|
||||||
|
serialize={identity}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Position</Forms.FormTitle>
|
||||||
|
<Select
|
||||||
|
isDisabled={settings.useNative === "always"}
|
||||||
|
placeholder="Notification Position"
|
||||||
|
options={[
|
||||||
|
{ label: "Bottom Right", value: "bottom-right", default: true },
|
||||||
|
{ label: "Top Right", value: "top-right" },
|
||||||
|
] satisfies Array<{ value: typeof settings["position"]; } & Record<string, any>>}
|
||||||
|
select={v => settings.position = v}
|
||||||
|
isSelected={v => v === settings.position}
|
||||||
|
serialize={identity}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Timeout</Forms.FormTitle>
|
||||||
|
<Forms.FormText className={Margins.bottom16}>Set to 0s to never automatically time out</Forms.FormText>
|
||||||
|
<Slider
|
||||||
|
disabled={settings.useNative === "always"}
|
||||||
|
markers={[0, 1000, 2500, 5000, 10_000, 20_000]}
|
||||||
|
minValue={0}
|
||||||
|
maxValue={20_000}
|
||||||
|
initialValue={settings.timeout}
|
||||||
|
onValueChange={v => settings.timeout = v}
|
||||||
|
onValueRender={v => (v / 1000).toFixed(2) + "s"}
|
||||||
|
onMarkerRender={v => (v / 1000) + "s"}
|
||||||
|
stickToMarkers={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Log Limit</Forms.FormTitle>
|
||||||
|
<Forms.FormText className={Margins.bottom16}>
|
||||||
|
The amount of notifications to save in the log until old ones are removed.
|
||||||
|
Set to <code>0</code> to disable Notification log and <code>∞</code> to never automatically remove old Notifications
|
||||||
|
</Forms.FormText>
|
||||||
|
<Slider
|
||||||
|
markers={[0, 25, 50, 75, 100, 200]}
|
||||||
|
minValue={0}
|
||||||
|
maxValue={200}
|
||||||
|
stickToMarkers={true}
|
||||||
|
initialValue={settings.logLimit}
|
||||||
|
onValueChange={v => settings.logLimit = v}
|
||||||
|
onValueRender={v => v === 200 ? "∞" : v}
|
||||||
|
onMarkerRender={v => v === 200 ? "∞" : v}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openNotificationSettingsModal() {
|
||||||
|
openModal(props => (
|
||||||
|
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
||||||
|
<ModalHeader>
|
||||||
|
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Notification Settings</Text>
|
||||||
|
<ModalCloseButton onClick={props.onClose} />
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalContent>
|
||||||
|
<NotificationSettings />
|
||||||
|
</ModalContent>
|
||||||
|
</ModalRoot>
|
||||||
|
));
|
||||||
|
}
|
|
@ -17,16 +17,19 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
||||||
import { Settings, useSettings } from "@api/Settings";
|
import { useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import DonateButton from "@components/DonateButton";
|
import DonateButton from "@components/DonateButton";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { identity } from "@utils/misc";
|
import { identity } from "@utils/misc";
|
||||||
import { relaunch, showItemInFolder } from "@utils/native";
|
import { relaunch, showItemInFolder } from "@utils/native";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Button, Card, Forms, React, Select, Slider, Switch } from "@webpack/common";
|
import { Button, Card, Forms, React, Select, Switch, TooltipContainer } from "@webpack/common";
|
||||||
|
import { ComponentType } from "react";
|
||||||
|
|
||||||
|
import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "..";
|
||||||
|
import { openNotificationSettingsModal } from "./NotificationSettings";
|
||||||
import { SettingsTab, wrapTab } from "./shared";
|
import { SettingsTab, wrapTab } from "./shared";
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-");
|
const cl = classNameFactory("vc-settings-");
|
||||||
|
@ -38,6 +41,18 @@ type KeysOfType<Object, Type> = {
|
||||||
[K in keyof Object]: Object[K] extends Type ? K : never;
|
[K in keyof Object]: Object[K] extends Type ? K : never;
|
||||||
}[keyof Object];
|
}[keyof Object];
|
||||||
|
|
||||||
|
const iconWithTooltip = (Icon: ComponentType<{ className?: string; }>, tooltip: string) => () => (
|
||||||
|
<TooltipContainer text={tooltip}>
|
||||||
|
<Icon className={cl("quick-actions-img")} />
|
||||||
|
</TooltipContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
const NotificationLogIcon = iconWithTooltip(LogIcon, "Open Notification Log");
|
||||||
|
const QuickCssIcon = iconWithTooltip(PaintbrushIcon, "Edit QuickCSS");
|
||||||
|
const RelaunchIcon = iconWithTooltip(RestartIcon, "Relaunch Discord");
|
||||||
|
const OpenSettingsDirIcon = iconWithTooltip(FolderIcon, "Open Settings Directory");
|
||||||
|
const OpenGithubIcon = iconWithTooltip(GithubIcon, "View Vencord's GitHub Repository");
|
||||||
|
|
||||||
function VencordSettings() {
|
function VencordSettings() {
|
||||||
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
|
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
|
||||||
fallbackValue: "Loading..."
|
fallbackValue: "Loading..."
|
||||||
|
@ -78,7 +93,7 @@ function VencordSettings() {
|
||||||
!IS_WEB && {
|
!IS_WEB && {
|
||||||
key: "transparent",
|
key: "transparent",
|
||||||
title: "Enable window transparency.",
|
title: "Enable window transparency.",
|
||||||
note: "You need a theme that supports transparency or this will do nothing. Will stop the window from being resizable. Requires a full restart"
|
note: "You need a theme that supports transparency or this will do nothing. WILL STOP THE WINDOW FROM BEING RESIZABLE!! Requires a full restart"
|
||||||
},
|
},
|
||||||
!IS_WEB && isWindows && {
|
!IS_WEB && isWindows && {
|
||||||
key: "winCtrlQ",
|
key: "winCtrlQ",
|
||||||
|
@ -97,44 +112,59 @@ function VencordSettings() {
|
||||||
<DonateCard image={donateImage} />
|
<DonateCard image={donateImage} />
|
||||||
<Forms.FormSection title="Quick Actions">
|
<Forms.FormSection title="Quick Actions">
|
||||||
<Card className={cl("quick-actions-card")}>
|
<Card className={cl("quick-actions-card")}>
|
||||||
<React.Fragment>
|
<Button
|
||||||
{!IS_WEB && (
|
onClick={openNotificationLogModal}
|
||||||
<Button
|
look={Button.Looks.BLANK}
|
||||||
onClick={relaunch}
|
>
|
||||||
size={Button.Sizes.SMALL}>
|
<NotificationLogIcon />
|
||||||
Restart Client
|
</Button>
|
||||||
</Button>
|
<Button
|
||||||
)}
|
onClick={() => VencordNative.quickCss.openEditor()}
|
||||||
|
look={Button.Looks.BLANK}
|
||||||
|
>
|
||||||
|
<QuickCssIcon />
|
||||||
|
</Button>
|
||||||
|
{!IS_WEB && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => VencordNative.quickCss.openEditor()}
|
onClick={relaunch}
|
||||||
size={Button.Sizes.SMALL}
|
look={Button.Looks.BLANK}
|
||||||
disabled={settingsDir === "Loading..."}>
|
>
|
||||||
Open QuickCSS File
|
<RelaunchIcon />
|
||||||
</Button>
|
</Button>
|
||||||
{!IS_WEB && (
|
)}
|
||||||
<Button
|
{!IS_WEB && (
|
||||||
onClick={() => showItemInFolder(settingsDir)}
|
|
||||||
size={Button.Sizes.SMALL}
|
|
||||||
disabled={settingsDirPending}>
|
|
||||||
Open Settings Folder
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => VencordNative.native.openExternal("https://github.com/Vendicated/Vencord")}
|
onClick={() => showItemInFolder(settingsDir)}
|
||||||
size={Button.Sizes.SMALL}
|
look={Button.Looks.BLANK}
|
||||||
disabled={settingsDirPending}>
|
disabled={settingsDirPending}
|
||||||
Open in GitHub
|
>
|
||||||
|
<OpenSettingsDirIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</React.Fragment>
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={() => VencordNative.native.openExternal("https://github.com/Vendicated/Vencord")}
|
||||||
|
look={Button.Looks.BLANK}
|
||||||
|
disabled={settingsDirPending}
|
||||||
|
>
|
||||||
|
<OpenGithubIcon />
|
||||||
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
|
|
||||||
<Forms.FormDivider />
|
<Forms.FormDivider />
|
||||||
|
|
||||||
<Forms.FormSection className={Margins.top16} title="Settings" tag="h5">
|
<Forms.FormSection className={Margins.top16} title="Settings" tag="h5">
|
||||||
<Forms.FormText className={Margins.bottom20}>
|
<Forms.FormText className={Margins.bottom20} style={{ color: "var(--text-muted)" }}>
|
||||||
Hint: You can change the position of this settings section in the settings of the "Settings" plugin!
|
Hint: You can change the position of this settings section in the
|
||||||
|
{" "}<Button
|
||||||
|
look={Button.Looks.BLANK}
|
||||||
|
style={{ color: "var(--text-link)", display: "inline-block" }}
|
||||||
|
onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}
|
||||||
|
>
|
||||||
|
settings of the Settings plugin
|
||||||
|
</Button>!
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
|
|
||||||
{Switches.map(s => s && (
|
{Switches.map(s => s && (
|
||||||
<Switch
|
<Switch
|
||||||
key={s.key}
|
key={s.key}
|
||||||
|
@ -212,94 +242,20 @@ function VencordSettings() {
|
||||||
serialize={identity} />
|
serialize={identity} />
|
||||||
</>}
|
</>}
|
||||||
|
|
||||||
{typeof Notification !== "undefined" && <NotificationSection settings={settings.notifications} />}
|
<Forms.FormSection className={Margins.top16} title="Vencord Notifications" tag="h5">
|
||||||
|
<Flex>
|
||||||
|
<Button onClick={openNotificationSettingsModal}>
|
||||||
|
Notification Settings
|
||||||
|
</Button>
|
||||||
|
<Button onClick={openNotificationLogModal}>
|
||||||
|
View Notification Log
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Forms.FormSection>
|
||||||
</SettingsTab>
|
</SettingsTab>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NotificationSection({ settings }: { settings: typeof Settings["notifications"]; }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Forms.FormTitle tag="h5">Notification Style</Forms.FormTitle>
|
|
||||||
{settings.useNative !== "never" && Notification?.permission === "denied" && (
|
|
||||||
<ErrorCard style={{ padding: "1em" }} className={Margins.bottom8}>
|
|
||||||
<Forms.FormTitle tag="h5">Desktop Notification Permission denied</Forms.FormTitle>
|
|
||||||
<Forms.FormText>You have denied Notification Permissions. Thus, Desktop notifications will not work!</Forms.FormText>
|
|
||||||
</ErrorCard>
|
|
||||||
)}
|
|
||||||
<Forms.FormText className={Margins.bottom8}>
|
|
||||||
Some plugins may show you notifications. These come in two styles:
|
|
||||||
<ul>
|
|
||||||
<li><strong>Vencord Notifications</strong>: These are in-app notifications</li>
|
|
||||||
<li><strong>Desktop Notifications</strong>: Native Desktop notifications (like when you get a ping)</li>
|
|
||||||
</ul>
|
|
||||||
</Forms.FormText>
|
|
||||||
<Select
|
|
||||||
placeholder="Notification Style"
|
|
||||||
options={[
|
|
||||||
{ label: "Only use Desktop notifications when Discord is not focused", value: "not-focused", default: true },
|
|
||||||
{ label: "Always use Desktop notifications", value: "always" },
|
|
||||||
{ label: "Always use Vencord notifications", value: "never" },
|
|
||||||
] satisfies Array<{ value: typeof settings["useNative"]; } & Record<string, any>>}
|
|
||||||
closeOnSelect={true}
|
|
||||||
select={v => settings.useNative = v}
|
|
||||||
isSelected={v => v === settings.useNative}
|
|
||||||
serialize={identity}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Position</Forms.FormTitle>
|
|
||||||
<Select
|
|
||||||
isDisabled={settings.useNative === "always"}
|
|
||||||
placeholder="Notification Position"
|
|
||||||
options={[
|
|
||||||
{ label: "Bottom Right", value: "bottom-right", default: true },
|
|
||||||
{ label: "Top Right", value: "top-right" },
|
|
||||||
] satisfies Array<{ value: typeof settings["position"]; } & Record<string, any>>}
|
|
||||||
select={v => settings.position = v}
|
|
||||||
isSelected={v => v === settings.position}
|
|
||||||
serialize={identity}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Timeout</Forms.FormTitle>
|
|
||||||
<Forms.FormText className={Margins.bottom16}>Set to 0s to never automatically time out</Forms.FormText>
|
|
||||||
<Slider
|
|
||||||
disabled={settings.useNative === "always"}
|
|
||||||
markers={[0, 1000, 2500, 5000, 10_000, 20_000]}
|
|
||||||
minValue={0}
|
|
||||||
maxValue={20_000}
|
|
||||||
initialValue={settings.timeout}
|
|
||||||
onValueChange={v => settings.timeout = v}
|
|
||||||
onValueRender={v => (v / 1000).toFixed(2) + "s"}
|
|
||||||
onMarkerRender={v => (v / 1000) + "s"}
|
|
||||||
stickToMarkers={false}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Log Limit</Forms.FormTitle>
|
|
||||||
<Forms.FormText className={Margins.bottom16}>
|
|
||||||
The amount of notifications to save in the log until old ones are removed.
|
|
||||||
Set to <code>0</code> to disable Notification log and <code>∞</code> to never automatically remove old Notifications
|
|
||||||
</Forms.FormText>
|
|
||||||
<Slider
|
|
||||||
markers={[0, 25, 50, 75, 100, 200]}
|
|
||||||
minValue={0}
|
|
||||||
maxValue={200}
|
|
||||||
stickToMarkers={true}
|
|
||||||
initialValue={settings.logLimit}
|
|
||||||
onValueChange={v => settings.logLimit = v}
|
|
||||||
onValueRender={v => v === 200 ? "∞" : v}
|
|
||||||
onMarkerRender={v => v === 200 ? "∞" : v}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={openNotificationLogModal}
|
|
||||||
disabled={settings.logLimit === 0}
|
|
||||||
>
|
|
||||||
Open Notification Log
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DonateCardProps {
|
interface DonateCardProps {
|
||||||
image: string;
|
image: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,16 +11,25 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-settings-quick-actions-card {
|
.vc-settings-quick-actions-card {
|
||||||
|
color: var(--text-normal);
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1em;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
flex-grow: 1;
|
gap: 1em;
|
||||||
flex-flow: row wrap;
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-settings-quick-actions-card button {
|
||||||
|
min-width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-settings-quick-actions-img {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
.vc-settings-donate {
|
.vc-settings-donate {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
@ -477,12 +477,17 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
|
|
||||||
isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) {
|
isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) {
|
||||||
if (!channel) return false;
|
try {
|
||||||
|
if (!channel) return false;
|
||||||
|
|
||||||
if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId);
|
if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId);
|
||||||
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
|
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
|
||||||
|
|
||||||
return !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || checkConnect && !PermissionStore.can(PermissionsBits.CONNECT, channel);
|
return !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || checkConnect && !PermissionStore.can(PermissionsBits.CONNECT, channel);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[ViewHiddenChannels#isHiddenChannel]: ", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
resolveGuildChannels(channels: Record<string | number, Array<{ channel: Channel; comparator: number; }> | string | number>, shouldIncludeHidden: boolean) {
|
resolveGuildChannels(channels: Record<string | number, Array<{ channel: Channel; comparator: number; }> | string | number>, shouldIncludeHidden: boolean) {
|
||||||
|
|
Loading…
Reference in a new issue