-
+
@@ -374,9 +419,10 @@ export default function PluginSettings() {
{ label: "Show Disabled", value: SearchStatus.DISABLED },
{ label: "Show Equicord", value: SearchStatus.EQUICORD },
{ label: "Show Vencord", value: SearchStatus.VENCORD },
- ...(totalUserPlugins > 0 ? [{ label: "Show Custom", value: SearchStatus.CUSTOM }] : []),
{ label: "Show New", value: SearchStatus.NEW },
- ]}
+ hasUserPlugins && { label: "Show UserPlugins", value: SearchStatus.USER_PLUGINS },
+ { label: "Show API Plugins", value: SearchStatus.API_PLUGINS },
+ ].filter(isTruthy)}
serialize={String}
select={onStatusChange}
isSelected={v => v === searchValue.status}
diff --git a/src/components/settings/tabs/plugins/styles.css b/src/components/settings/tabs/plugins/styles.css
index 70288b64a..f2c8a38f3 100644
--- a/src/components/settings/tabs/plugins/styles.css
+++ b/src/components/settings/tabs/plugins/styles.css
@@ -19,7 +19,7 @@
.vc-plugins-grid {
margin-top: 16px;
display: grid;
- grid-gap: 16px;
+ gap: 16px;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
@@ -29,16 +29,16 @@
padding: 0;
background: transparent;
margin-right: 8px;
- color: var(--icon-secondary);
+ color: var(--icon-muted);
transition: background-color .2s ease;
&:hover {
- color: var(--interactive-hover)
+ color: var(--interactive-icon-hover)
}
}
.vc-plugins-settings-button:hover {
- color: var(--interactive-hover);
+ color: var(--interactive-icon-hover);
}
.vc-plugins-filter-controls {
@@ -78,23 +78,23 @@
.vc-plugins-restart-card {
padding: 1em;
- background: var(--info-warning-background);
- border: 1px solid var(--info-warning-foreground);
- color: var(--info-warning-foreground);
+ background: var(--icon-status-idle) !important;
+ border: 1px solid var(--icon-status-idle) !important;
+ color: var(--text-status-idle);
}
.vc-plugins-info-icon:not(:hover, :focus) {
- color: var(--text-muted);
+ color: var(--text-subtle);
}
.vc-plugins-restart-button {
- background-color: var(--background-color) !important;
- border: 1px solid var(--info-warning-foreground) !important;
+ background-color: var(--background-base-low) !important;
+ border: 1px solid var(--text-status-idle) !important;
margin-top: 0.5em;
}
.vc-plugins-restart-button:hover {
- background-color: var(--info-warning-foreground) !important;
+ background-color: var(--text-status-idle) !important;
}
.vc-plugin-stats {
@@ -112,7 +112,7 @@
.vc-plugins-info-card:hover,
.vc-plugin-stats:hover {
- background-color: var(--background-tertiary) !important;
+ background-color: var(--background-base-low) !important;
transform: translateY(-1px);
box-shadow: var(--elevation-high);
}
@@ -159,7 +159,7 @@
}
.text-danger {
- color: var(--text-danger) !important;
+ color: var(--text-feedback-critical) !important;
font-size: 1.2rem;
font-weight: bold;
}
@@ -175,29 +175,29 @@
.vc-plugins-disable-warning,
.vc-plugin-modal-disable-warning {
- background-color: color-mix(in srgb, var(--button-danger-background) 10%, transparent) !important;
+ background-color: color-mix(in srgb, var(--redesign-button-danger-background) 10%, transparent) !important;
border-style: solid !important;
border-width: .1rem !important;
- border-color: color-mix(in srgb, var(--button-danger-background) 80%, transparent) !important;
+ border-color: color-mix(in srgb, var(--redesign-button-danger-background) 80%, transparent) !important;
border-radius: 5px !important;
- color: color-mix(in srgb, var(--button-danger-background) 100%, transparent) !important;
+ color: color-mix(in srgb, var(--redesign-button-danger-background) 100%, transparent) !important;
}
.vc-plugins-disable-warning:hover,
.vc-plugin-modal-disable-warning:hover {
- background-color: color-mix(in srgb, var(--button-danger-background-hover) 40%, transparent) !important;
+ background-color: color-mix(in srgb, var(--redesign-button-danger-background) 40%, transparent) !important;
}
.vc-plugins-confirm-reset,
.vc-plugin-modal-confirm-reset {
- background-color: var(--button-danger-background) !important;
- color: var(--header-primary);
+ background-color: var(--redesign-button-danger-background) !important;
+ color: var(--redesign-button-danger-pressed-text);
}
.vc-plugins-confirm-reset:hover,
.vc-plugin-modal-confirm-reset:hover {
- background-color: var(--button-danger-background-hover) !important;
- filter: saturate(80%);
+ background-color: var(--redesign-button-danger-pressed-background) !important;
+ color: var(--redesign-button-danger-pressed-text);
}
.vc-modal-close-button {
@@ -224,7 +224,7 @@
/* Specific to disable all modal */
.warning-text {
- color: var(--text-danger) !important;
+ color: var(--text-feedback-critical) !important;
font-size: 1.2rem !important;
}
@@ -250,20 +250,20 @@
}
.visual-refresh .vc-plugins-info-card {
- background-color: var(--card-primary-bg) !important;
+ background-color: var(--background-surface-high) !important;
border: 1px solid var(--border-subtle) !important;
&:hover {
- background-color: var(--card-primary-bg) !important;
+ background-color: var(--background-surface-high) !important;
}
}
.visual-refresh .vc-plugin-stats {
- background-color: var(--card-primary-bg) !important;
+ background-color: var(--background-surface-high) !important;
border: 1px solid var(--border-subtle) !important;
&:hover {
- background-color: var(--card-primary-bg) !important;
+ background-color: var(--background-surface-high) !important;
}
}
@@ -286,4 +286,16 @@
height: 22px;
margin-top: 2px;
margin-right: 8px;
+}
+
+.vc-plugins-stats-container {
+ margin-top: 16px;
+ gap: 16px;
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+}
+
+.vc-plugins-ui-elements {
+ margin-top: 16px;
}
\ No newline at end of file
diff --git a/src/components/settings/tabs/styles.css b/src/components/settings/tabs/styles.css
index eacb524c5..83003366c 100644
--- a/src/components/settings/tabs/styles.css
+++ b/src/components/settings/tabs/styles.css
@@ -33,15 +33,15 @@
.vc-warning-card {
padding: 1em;
- background: var(--info-warning-background);
- border: 1px solid var(--info-warning-foreground);
- color: var(--info-warning-foreground);
+ background: var(--background-feedback-warning);
+ border: 1px solid var(--text-status-idle);
+ color: var(--text-status-idle);
}
.vc-backup-restore-card {
- background-color: var(--info-warning-background);
- border-color: var(--info-warning-foreground);
- color: var(--info-warning-foreground);
+ background-color: var(--background-feedback-warning);
+ border-color: var(--text-status-idle);
+ color: var(--text-status-idle);
}
.vc-settings-theme-links {
@@ -49,7 +49,7 @@
display: inline-block !important;
color: var(--text-default) !important;
padding: 0.5em 1em;
- border: 1px solid var(--input-border);
+ border: 1px solid var(--input-border-default);
max-height: unset;
background-color: transparent;
box-sizing: border-box;
@@ -61,7 +61,7 @@
}
.vc-settings-theme-links::placeholder {
- color: var(--text-muted) !important;
+ color: var(--text-subtle) !important;
}
.vc-settings-theme-links:focus {
@@ -71,18 +71,18 @@
.vc-cloud-settings-sync-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
- grid-gap: 1em;
+ gap: 1em;
}
.vc-cloud-erase-data-danger-btn {
color: var(--white-500);
- background-color: var(--button-danger-background);
+ background-color: var(--redesign-button-danger-background);
}
-.vc-updater-modal {
+.vc-settings-modal {
padding: 1.5em !important;
}
-.vc-updater-modal-close-button {
+.vc-settings-modal-close {
float: right;
}
\ No newline at end of file
diff --git a/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx b/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx
index 38b67ac35..f38741e2f 100644
--- a/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx
+++ b/src/components/settings/tabs/sync/BackupAndRestoreTab.tsx
@@ -16,67 +16,103 @@
* along with this program. If not, see .
*/
-import { BaseText } from "@components/BaseText";
+import { downloadSettingsBackup, uploadSettingsBackup } from "@api/SettingsSync/offline";
+import { Alert } from "@components/Alert";
+import { Button } from "@components/Button";
+import { Divider } from "@components/Divider";
import { Flex } from "@components/Flex";
+import { Heading } from "@components/Heading";
+import { Paragraph } from "@components/Paragraph";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { Margins } from "@utils/margins";
-import { classes } from "@utils/misc";
-import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync";
-import { Button, Card } from "@webpack/common";
function BackupAndRestoreTab() {
return (
-
-
-
- Warning
- Importing a settings file will overwrite your current settings.
-
-
-
- You can import and export your Equicord settings as a JSON file.
- This allows you to easily transfer your settings to another device,
- or recover your settings after reinstalling Equicord or Discord.
-
-
- Settings Export contains:
-
- - — Custom QuickCSS
- - — Theme Links
- - — Plugin Settings
- - — Plugin DataStores (e.g. Timezones or IRememberYou)
-
-
-
+
+ Backup & Restore
+
+ Import and export your Equicord settings as a JSON file. This allows you to easily transfer your settings to another device, or recover them after reinstalling Equicord or Discord.
+
+
+
+ Importing a settings file will overwrite your current settings. Make sure to export a backup first if you want to keep your current configuration.
+
+
+ What's included in a backup
+
+ • Custom QuickCSS
+ • Theme Links
+ • Plugin Settings
+ • DataStore Data
+
+
+
+
+ Import Settings
+
+ Select a previously exported settings file to restore your configuration. This will replace all your current settings with the ones from the backup.
+
+
+
+
+
+
+
+
+
+ Export Settings
+
+ Download your current settings as a backup file. You can export everything at once, or choose to export only specific parts of your configuration.
+
+
+
+
diff --git a/src/components/settings/tabs/sync/CloudTab.tsx b/src/components/settings/tabs/sync/CloudTab.tsx
index 046b609e9..ad6aec597 100644
--- a/src/components/settings/tabs/sync/CloudTab.tsx
+++ b/src/components/settings/tabs/sync/CloudTab.tsx
@@ -16,20 +16,28 @@
* along with this program. If not, see .
*/
-import { showNotification } from "@api/Notifications";
-import { Settings, useSettings } from "@api/Settings";
+import { useSettings } from "@api/Settings";
+import { authorizeCloud, deauthorizeCloud } from "@api/SettingsSync/cloudSetup";
+import { deleteCloudSettings, eraseAllCloudData, getCloudSettings, putCloudSettings } from "@api/SettingsSync/cloudSync";
+import { Alert } from "@components/Alert";
+import { Button } from "@components/Button";
import { CheckedTextInput } from "@components/CheckedTextInput";
import { Divider } from "@components/Divider";
+import { Flex } from "@components/Flex";
import { FormSwitch } from "@components/FormSwitch";
-import { Grid } from "@components/Grid";
import { Heading } from "@components/Heading";
import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph";
+import { QuickAction, QuickActionCard } from "@components/settings/QuickAction";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
-import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
import { Margins } from "@utils/margins";
-import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync";
-import { Alerts, Button, Tooltip, useState } from "@webpack/common";
+import { findComponentByCodeLazy } from "@webpack";
+import { Alerts, useState } from "@webpack/common";
+
+const UploadIcon = findComponentByCodeLazy("M12.7 3.3a1 1 0 0 0-1.4 0l-5 5a1 1 0 0 0 1.4 1.4L11 6.42V20");
+const DownloadIcon = findComponentByCodeLazy("M12.7 20.7a1 1 0 0 1-1.4 0l-5-5a1 1 0 1 1 1.4-1.4l3.3 3.29V4");
+const TrashIcon = findComponentByCodeLazy("2.81h8.36a3");
+const SkullIcon = findComponentByCodeLazy("m13.47 1 .07.04c.45.06");
function validateUrl(url: string) {
try {
@@ -40,93 +48,17 @@ function validateUrl(url: string) {
}
}
-async function eraseAllData() {
- const res = await fetch(new URL("/v1/", getCloudUrl()), {
- method: "DELETE",
- headers: { Authorization: await getCloudAuth() }
- });
-
- if (!res.ok) {
- cloudLogger.error(`Failed to erase data, API returned ${res.status}`);
- showNotification({
- title: "Cloud Integrations",
- body: `Could not erase all data (API returned ${res.status}), please contact support.`,
- color: "var(--red-360)"
- });
- return;
- }
-
- Settings.cloud.authenticated = false;
- await deauthorizeCloud();
-
- showNotification({
- title: "Cloud Integrations",
- body: "Successfully erased all data.",
- color: "var(--green-360)"
- });
-}
-
-function SettingsSyncSection() {
- const { cloud } = useSettings(["cloud.authenticated", "cloud.settingsSync"]);
- const sectionEnabled = cloud.authenticated && cloud.settingsSync;
-
- return (
-
- Settings Sync
-
-
- Synchronize your settings to the cloud. This allows easy synchronization across multiple devices with
- minimal effort.
-
- { cloud.settingsSync = v; }}
- disabled={!cloud.authenticated}
- />
-
-
-
- {({ onMouseLeave, onMouseEnter }) => (
-
- )}
-
-
-
-
- );
-}
-
function CloudTab() {
- const settings = useSettings(["cloud.authenticated", "cloud.url"]);
+ const settings = useSettings(["cloud.authenticated", "cloud.url", "cloud.settingsSync"]);
const [inputKey, setInputKey] = useState(0);
+ const { cloud } = settings;
+ const isAuthenticated = cloud.authenticated;
+ const syncEnabled = isAuthenticated && cloud.settingsSync;
+
async function changeUrl(url: string) {
- settings.cloud.url = url;
- settings.cloud.authenticated = false;
+ cloud.url = url;
+ cloud.authenticated = false;
await deauthorizeCloud();
await authorizeCloud();
@@ -135,86 +67,153 @@ function CloudTab() {
}
return (
-
-
- Cloud Settings
+
+ Cloud Integration
+
+ Equicord's cloud integration allows you to sync your settings across multiple devices and Discord installations. Your data is securely stored and can be easily restored at any time.
+
-
- Equicord comes with a cloud integration allowing settings to be synced across apps and devices.
-
- We use our own Equicloud backend to provide our cloud instance with enhanced features.
-
- Our privacy policy allows you to see what information we store, how we use it, and data retention.
-
- Equicloud is BSD 3.0 licensed so you can host it yourself if you would like.
-
- You can swap between Equicord and Vencord's different cloud instances below if needed.
-
- {
- if (v)
- authorizeCloud();
- else
- settings.cloud.authenticated = v;
- }}
- />
- Backend URL
-
- Which backend to use when using cloud integrations.
-
- {
- settings.cloud.url = v;
- settings.cloud.authenticated = false;
+
+ We use our own Equicloud backend with enhanced features.
+ View our privacy policy to see what we store and how we use your data.
+ Equicloud is BSD 3.0 licensed, so you can self-host if preferred.
+
+
+ {
+ if (v)
+ authorizeCloud();
+ else
+ cloud.authenticated = v;
+ }}
+ hideBorder
+ />
+
+
+
+ Cloud Backend
+
+ Choose which cloud backend to use for storing your settings. You can switch between Equicord's and Vencord's cloud services, or use a self-hosted instance.
+
+
+ Current: {cloud.url}
+
+
+ {
+ cloud.url = v;
+ cloud.authenticated = false;
+ await deauthorizeCloud();
+ }}
+ validate={validateUrl}
+ />
+
+
+
+
+
+
-
-
-
-
-
-
+
-
-
-
+ Settings Sync
+
+ Synchronize your Equicord settings to the cloud. This makes it easy to keep your configuration consistent across multiple devices without manual import/export.
+
+
+ { cloud.settingsSync = v; }}
+ disabled={!isAuthenticated}
+ hideBorder
+ />
+
+ {isAuthenticated && (
+
+ putCloudSettings(true)}
+ disabled={!syncEnabled}
+ />
+ getCloudSettings(true, true)}
+ disabled={!syncEnabled}
+ />
+
+ )}
+
+ {!isAuthenticated && (
+
+ Enable cloud integration above to use settings sync features.
+
+ )}
+
+
+
+ Danger Zone
+
+ Permanently delete all your data from the cloud. This action cannot be undone and will remove all synced settings and any other data stored on the cloud backend.
+
+
+
+
+
+
);
}
diff --git a/src/components/settings/tabs/updater/Components.tsx b/src/components/settings/tabs/updater/Components.tsx
index 935c24e46..8d9f34afb 100644
--- a/src/components/settings/tabs/updater/Components.tsx
+++ b/src/components/settings/tabs/updater/Components.tsx
@@ -4,15 +4,17 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
+import { Button } from "@components/Button";
+import { Card } from "@components/Card";
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex";
import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph";
+import { Span } from "@components/Span";
import { Margins } from "@utils/margins";
-import { classes } from "@utils/misc";
import { relaunch } from "@utils/native";
import { changes, checkForUpdates, update, updateError } from "@utils/updater";
-import { Alerts, Button, Card, React, Toasts, useState } from "@webpack/common";
+import { Alerts, React, Toasts, useState } from "@webpack/common";
import { runWithDispatch } from "./runWithDispatch";
@@ -31,25 +33,26 @@ export function HashLink({ repo, hash, disabled = false }: { repo: string, hash:
export function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) {
return (
-
- {updates.map(({ hash, author, message }) => (
+
+ {updates.map(({ hash, author, message }, i) => (
-
-
-
-
-
- {message} - {author}
-
+
+
+
+
+
+ {message}
+
+
+ — {author}
+
+
))}
@@ -59,8 +62,8 @@ export function Changes({ updates, repo, repoPending }: CommonProps & { updates:
export function Newer(props: CommonProps) {
return (
<>
-
- Your local copy has more recent commits. Please stash or reset them.
+
+ Your local copy has more recent commits than the remote repository. This usually happens when you've made local changes. Please stash or reset them before updating.
>
@@ -76,25 +79,35 @@ export function Updatable(props: CommonProps) {
return (
<>
- {!updates && updateError ? (
- <>
- Failed to check updates. Check the console for more info
-
- {updateError.stderr || updateError.stdout || "An unknown error occurred"}
-
- >
- ) : (
-
- {isOutdated ? (updates.length === 1 ? "There is 1 Update" : `There are ${updates.length} Updates`) : "Up to Date!"}
-
- )}
+
+
{isOutdated && (
)}
-
+ {!updates && updateError ? (
+ <>
+ Error checking for updates
+
+ {updateError.stderr || updateError.stdout || "An unknown error occurred"}
+
+ >
+ ) : isOutdated ? (
+ <>
+
+ There {updates.length === 1 ? "is 1 update" : `are ${updates.length} updates`} available. Click the button below to download and install.
+
+
+ >
+ ) : (
+
+ You're running the latest version of Equicord.
+
+ )}
>
);
}
diff --git a/src/components/settings/tabs/updater/index.tsx b/src/components/settings/tabs/updater/index.tsx
index 4bca119fe..c964ba4ee 100644
--- a/src/components/settings/tabs/updater/index.tsx
+++ b/src/components/settings/tabs/updater/index.tsx
@@ -18,155 +18,25 @@
import { useSettings } from "@api/Settings";
import { Divider } from "@components/Divider";
-import { ErrorCard } from "@components/ErrorCard";
-import { Flex } from "@components/Flex";
import { FormSwitch } from "@components/FormSwitch";
import { Heading } from "@components/Heading";
import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph";
-import { handleSettingsTabError, SettingsTab, wrapTab } from "@components/settings";
+import { SettingsTab, wrapTab } from "@components/settings";
import { Margins } from "@utils/margins";
-import { classes } from "@utils/misc";
-import { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
-import { relaunch } from "@utils/native";
import { useAwaiter } from "@utils/react";
-import { changes, checkForUpdates, getRepo, isNewer, update, updateError, UpdateLogger } from "@utils/updater";
-import { Alerts, Button, Parser, React, Toasts } from "@webpack/common";
+import { getRepo, isNewer, UpdateLogger } from "@utils/updater";
+import { React } from "@webpack/common";
import gitHash from "~git-hash";
-import { Changes, HashLink } from "./Components";
-
-function withDispatcher(dispatcher: React.Dispatch>, action: () => any) {
- return async () => {
- dispatcher(true);
- try {
- await action();
- } catch (e: any) {
- UpdateLogger.error("Failed to update", e);
-
- let err: string;
- if (!e) {
- err = "An unknown error occurred (error is undefined).\nPlease try again.";
- } else if (e.code && e.cmd) {
- const { code, path, cmd, stderr } = e;
-
- if (code === "ENOENT")
- err = `Command \`${path}\` not found.\nPlease install it and try again`;
- else {
- err = `An error occurred while running \`${cmd}\`:\n`;
- err += stderr || `Code \`${code}\`. See the console for more info`;
- }
-
- } else {
- err = "An unknown error occurred. See the console for more info.";
- }
-
- Alerts.show({
- title: "Oops!",
- body: (
-
- {err.split("\n").map((line, idx) => {Parser.parse(line)}
)}
-
- )
- });
- }
- finally {
- dispatcher(false);
- }
- };
-}
+import { HashLink, Newer, Updatable } from "./Components";
interface CommonProps {
repo: string;
repoPending: boolean;
}
-function Updatable(props: CommonProps) {
- const [updates, setUpdates] = React.useState(changes);
- const [isChecking, setIsChecking] = React.useState(false);
- const [isUpdating, setIsUpdating] = React.useState(false);
- const isOutdated = (updates?.length ?? 0) > 0;
-
- return (
- <>
-
- {isOutdated && }
-
-
- {!updates && updateError ? (
- <>
- Failed to check updates. Check the console for more info
-
- {updateError.stderr || updateError.stdout || "An unknown error occurred"}
-
- >
- ) : (
-
- {isOutdated ? (updates.length === 1 ? "There is 1 Update" : `There are ${updates.length} Updates`) : "Up to Date!"}
-
- )}
-
- {isOutdated && }
- >
- );
-}
-
-function Newer(props: CommonProps) {
- return (
- <>
-
- Your local copy has more recent commits. Please stash or reset them.
-
-
- >
- );
-}
-
function Updater() {
const settings = useSettings(["autoUpdate", "autoUpdateNotification"]);
@@ -183,25 +53,35 @@ function Updater() {
};
return (
-
- Updater Settings
+
+ Update Preferences
+
+ Control how Equicord keeps itself up to date. You can choose to update automatically in the background or be notified when new updates are available.
+
+
settings.autoUpdate = v}
+ hideBorder
/>
settings.autoUpdateNotification = v}
title="Get notified when an automatic update completes"
- description="Shows a notification when Equicord automatically updates"
+ description="Receive a notification when Equicord finishes downloading an update in the background, so you know when to restart Discord."
disabled={!settings.autoUpdate}
+ hideBorder
/>
- Repo
+
-
+ Repository
+
+ This is the GitHub repository where Equicord fetches updates from.
+
+
{repoPending
? repo
: err
@@ -215,30 +95,14 @@ function Updater() {
{" "}()
-
-
- Updates
+
+ Updates
{isNewer ? : }
-
+
);
}
-export default IS_UPDATER_DISABLED ? null : wrapTab(Updater, "Updater");
-
-export const openUpdaterModal = IS_UPDATER_DISABLED ? null : function () {
- const UpdaterTab = wrapTab(Updater, "Updater");
-
- try {
- openModal(wrapTab((modalProps: ModalProps) => (
-
-
-
-
-
-
- ), "UpdaterModal"));
- } catch {
- handleSettingsTabError();
- }
-};
+export default IS_UPDATER_DISABLED
+ ? null
+ : wrapTab(Updater, "Updater");
diff --git a/src/components/settings/tabs/vencord/DonateButton.tsx b/src/components/settings/tabs/vencord/DonateButton.tsx
index a02a4cbf6..0d63f2b87 100644
--- a/src/components/settings/tabs/vencord/DonateButton.tsx
+++ b/src/components/settings/tabs/vencord/DonateButton.tsx
@@ -4,22 +4,27 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
-import { DonateButton } from "@components/settings";
+import { Flex } from "@components/Flex";
+import { DonateButton, InviteButton } from "@components/settings/DonateButton";
+import BadgeAPI from "@plugins/_api/badges";
import { DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants";
-import { Button, GuildMemberStore } from "@webpack/common";
-import BadgeAPI from "plugins/_api/badges";
+import { GuildMemberStore } from "@webpack/common";
export const isDonor = (userId: string) => !!(
BadgeAPI.getDonorBadges(userId)?.length > 0
|| GuildMemberStore?.getMember(VC_GUILD_ID, userId)?.roles.includes(DONOR_ROLE_ID)
);
-export function DonateButtonComponent() {
+export function DonateButtonComponent({ donated = false }) {
return (
-
+
+
+
+
);
}
diff --git a/src/components/settings/tabs/vencord/NotificationSettings.tsx b/src/components/settings/tabs/vencord/NotificationSettings.tsx
index 084ee118f..36fcdfc2b 100644
--- a/src/components/settings/tabs/vencord/NotificationSettings.tsx
+++ b/src/components/settings/tabs/vencord/NotificationSettings.tsx
@@ -7,6 +7,7 @@
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { useSettings } from "@api/Settings";
import { BaseText } from "@components/BaseText";
+import { Button } from "@components/Button";
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex";
import { FormSwitch } from "@components/FormSwitch";
@@ -15,7 +16,7 @@ import { Paragraph } from "@components/Paragraph";
import { Margins } from "@utils/margins";
import { identity } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
-import { Button, Select, Slider } from "@webpack/common";
+import { Select, Slider } from "@webpack/common";
export function NotificationSection() {
return (
@@ -53,7 +54,7 @@ export function openNotificationSettingsModal() {
}
function NotificationSettings() {
- const settings = useSettings().notifications;
+ const settings = useSettings(["notifications.*"]).notifications;
return (
diff --git a/src/components/settings/tabs/vencord/VencordTab.css b/src/components/settings/tabs/vencord/VencordTab.css
index 2b2394c71..cc4a52c54 100644
--- a/src/components/settings/tabs/vencord/VencordTab.css
+++ b/src/components/settings/tabs/vencord/VencordTab.css
@@ -4,7 +4,7 @@
}
.vc-donatebutton {
- background-color: var(--background-color) !important;
+ background-color: var(--background-base-low) !important;
border-color: var(--vc-donatebutton) !important;
filter: brightness(1);
border-width: .1rem !important;
@@ -18,11 +18,11 @@
.vc-joindiscordbutton {
- background-color: var(--button-secondary-background) !important;
+ background-color: var(--redesign-button-secondary-background) !important;
}
.vc-joindiscordbutton:hover {
- background-color: var(--button-secondary-background-hover) !important;
+ background-color: var(--redesign-button-secondary-pressed-background) !important;
}
.vc-settings-card-buttons {
@@ -47,3 +47,8 @@
text-align: center;
margin-top: 10px;
}
+
+.vc-vencord-tab-settings-link {
+ color: var(--text-link);
+ display: inline-block;
+}
\ No newline at end of file
diff --git a/src/components/settings/tabs/vencord/index.tsx b/src/components/settings/tabs/vencord/index.tsx
index 0c9afbde5..79dc4f987 100644
--- a/src/components/settings/tabs/vencord/index.tsx
+++ b/src/components/settings/tabs/vencord/index.tsx
@@ -8,23 +8,27 @@ import "./VencordTab.css";
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { useSettings } from "@api/Settings";
+import { classNameFactory } from "@api/Styles";
+import { Alert } from "@components/Alert";
+import { Button } from "@components/Button";
import { Divider } from "@components/Divider";
+import { Flex } from "@components/Flex";
import { FormSwitch } from "@components/FormSwitch";
import { Heading } from "@components/Heading";
import { FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "@components/Icons";
import { Paragraph } from "@components/Paragraph";
import { openContributorModal, openPluginModal, SettingsTab, wrapTab } from "@components/settings";
-import { DonateButton, InviteButton } from "@components/settings/DonateButton";
import { QuickAction, QuickActionCard } from "@components/settings/QuickAction";
import { SpecialCard } from "@components/settings/SpecialCard";
import { gitRemote } from "@shared/vencordUserAgent";
-import { DONOR_ROLE_ID, GUILD_ID, VC_DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants";
+import { DONOR_ROLE_ID, GUILD_ID, IS_MAC, IS_WINDOWS, VC_DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants";
import { Margins } from "@utils/margins";
import { identity, isAnyPluginDev } from "@utils/misc";
import { relaunch } from "@utils/native";
-import { Button, Flex, GuildMemberStore, React, Select, UserStore } from "@webpack/common";
+import { GuildMemberStore, React, Select, UserStore } from "@webpack/common";
import BadgeAPI from "plugins/_api/badges";
+import { DonateButtonComponent } from "./DonateButton";
import { openNotificationSettingsModal } from "./NotificationSettings";
const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png";
@@ -36,6 +40,8 @@ const COZY_CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/102653307095587233
const DONOR_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070116305436712.png?size=2048";
const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070166481895484.png?size=2048";
+const cl = classNameFactory("vc-vencord-tab-");
+
type KeysOfType
-
+
{decodedContent.map((content, index) => (
: null;
};
+interface TabNumberBadgeProps {
+ number: number;
+ position: "left" | "right";
+ isSelected: boolean;
+ isCompact: boolean;
+ isHovered: boolean;
+}
+
+export const TabNumberBadge = ({ number, position, isSelected, isCompact, isHovered }: TabNumberBadgeProps) => {
+ // hide badge if:
+ // 1. tab is currently selected
+ // 2. tab is compact AND not hovered
+ const shouldHide = isSelected || (isCompact && !isHovered);
+
+ if (shouldHide) return null;
+
+ return (
+
+ {number}
+
+ );
+};
+
function ChannelTabContent(props: ChannelTabsProps & {
guild?: Guild,
channel?: Channel;
@@ -113,11 +139,15 @@ function ChannelTabContent(props: ChannelTabsProps & {
const [isTyping, status, isMobile] = useStateFromStores(
[TypingStore, PresenceStore],
- () => [
- !!((Object.keys(TypingStore.getTypingUsers(props.channelId)) as string[]).filter(id => id !== userId).length),
- PresenceStore.getStatus(recipients?.[0]) as string,
- PresenceStore.isMobileOnline(recipients?.[0]) as boolean
- ]
+ () => {
+ const recipientId = recipients?.[0] ?? "";
+
+ return [
+ !!((Object.keys(TypingStore.getTypingUsers(props.channelId)) as string[]).filter(id => id !== userId).length),
+ PresenceStore.getStatus(recipientId),
+ PresenceStore.isMobileOnline(recipientId)
+ ];
+ }
);
if (guild) {
@@ -204,7 +234,9 @@ function ChannelTabContent(props: ChannelTabsProps & {
"__shop__": { label: "Shop", Icon: ShopIcon },
"__library__": { label: "Library", Icon: () => LibraryIcon(20, 20) },
"__discovery__": { label: "Discovery", Icon: DiscoveryIcon },
- "__nitro__": { label: "Nitro", Icon: NitroIcon }
+ "__nitro__": { label: "Nitro", Icon: NitroIcon },
+ "__icymi__": { label: "ICYMI", Icon: ICYMIIcon },
+ "__activity__": { label: "Activity", Icon: ActivityIcon },
};
const pageConfig = specialPagesConfig[channelId];
@@ -245,6 +277,9 @@ export default function ChannelTab(props: ChannelTabsProps & { index: number; })
const [isClosing, setIsClosing] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [isDropTarget, setIsDropTarget] = useState(false);
+ const [isHovered, setIsHovered] = useState(false);
+
+ const { showTabNumbers, tabNumberPosition } = settings.use(["showTabNumbers", "tabNumberPosition"]);
useEffect(() => {
if (isEntering) {
@@ -271,6 +306,7 @@ export default function ChannelTab(props: ChannelTabsProps & { index: number; })
const startX = e.clientX;
const startWidth = ref.current?.getBoundingClientRect().width || 0;
const baseWidth = 192; // 12rem in pixels (assuming 16px base font)
+ let pendingScale = settings.store.tabWidthScale;
document.body.style.cursor = "ew-resize";
document.body.style.userSelect = "none";
@@ -282,7 +318,12 @@ export default function ChannelTab(props: ChannelTabsProps & { index: number; })
// 50% and 200% scale
const clampedScale = Math.max(0.5, Math.min(2, newScale));
- settings.store.tabWidthScale = Math.round(clampedScale * 100);
+ pendingScale = Math.round(clampedScale * 100);
+
+ // update CSS variable immediately for visual feedback
+ if (ref.current) {
+ ref.current.style.setProperty("--tab-width-scale", String(pendingScale / 100));
+ }
};
const handleMouseUp = () => {
@@ -290,6 +331,12 @@ export default function ChannelTab(props: ChannelTabsProps & { index: number; })
document.removeEventListener("mouseup", handleMouseUp);
document.body.style.cursor = "";
document.body.style.userSelect = "";
+
+ settings.store.tabWidthScale = pendingScale;
+
+ if (ref.current) {
+ ref.current.style.removeProperty("--tab-width-scale");
+ }
};
document.addEventListener("mousemove", handleMouseMove);
@@ -396,6 +443,8 @@ export default function ChannelTab(props: ChannelTabsProps & { index: number; })
})}
key={index}
ref={ref}
+ onMouseEnter={() => setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
onAuxClick={e => {
if (e.button === 1 /* middle click */)
closeTab(id);
@@ -410,7 +459,29 @@ export default function ChannelTab(props: ChannelTabsProps & { index: number; })
className={cl("tab-inner")}
data-compact={compact}
>
+ {/* left position badge */}
+ {showTabNumbers && tabNumberPosition === "left" && (
+
+ )}
+
+
+ {/* right position badge */}
+ {showTabNumbers && tabNumberPosition === "right" && (
+
+ )}
@@ -421,7 +492,7 @@ export default function ChannelTab(props: ChannelTabsProps & { index: number; })
setTimeout(() => closeTab(id), 150);
}}
>
-