diff --git a/src/Vencord.ts b/src/Vencord.ts
index 841fd957..dcef98aa 100644
--- a/src/Vencord.ts
+++ b/src/Vencord.ts
@@ -39,7 +39,7 @@ import { get as dsGet } from "./api/DataStore";
import { NotificationData, showNotification } from "./api/Notifications";
import { initPluginManager, PMLogger, startAllPlugins } from "./api/PluginManager";
import { PlainSettings, Settings, SettingsStore } from "./api/Settings";
-import { getCloudSettings, putCloudSettings } from "./api/SettingsSync/cloudSync";
+import { getCloudSettings, putCloudSettings, shouldCloudSync } from "./api/SettingsSync/cloudSync";
import { localStorage } from "./utils/localStorage";
import { relaunch } from "./utils/native";
import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
@@ -52,6 +52,11 @@ if (IS_REPORTER) {
}
async function syncSettings() {
+ if (localStorage.Vencord_cloudSyncDirection === undefined) {
+ // by default, sync bi-directionally
+ localStorage.Vencord_cloudSyncDirection = "both";
+ }
+
// pre-check for local shared settings
if (
Settings.cloud.authenticated &&
@@ -70,12 +75,12 @@ async function syncSettings() {
if (
Settings.cloud.settingsSync && // if it's enabled
- Settings.cloud.authenticated // if cloud integrations are enabled
+ Settings.cloud.authenticated && // if cloud integrations are enabled
+ localStorage.Vencord_cloudSyncDirection !== "manual" // if we're not in manual mode
) {
- if (localStorage.Vencord_settingsDirty) {
+ if (localStorage.Vencord_settingsDirty && shouldCloudSync("push")) {
await putCloudSettings();
- delete localStorage.Vencord_settingsDirty;
- } else if (await getCloudSettings(false)) { // if we synchronized something (false means no sync)
+ } else if (shouldCloudSync("pull") && await getCloudSettings(false)) { // if we synchronized something (false means no sync)
// we show a notification here instead of allowing getCloudSettings() to show one to declutter the amount of
// potential notifications that might occur. getCloudSettings() will always send a notification regardless if
// there was an error to notify the user, but besides that we only want to show one notification instead of all
@@ -90,9 +95,8 @@ async function syncSettings() {
}
const saveSettingsOnFrequentAction = debounce(async () => {
- if (Settings.cloud.settingsSync && Settings.cloud.authenticated) {
+ if (Settings.cloud.settingsSync && Settings.cloud.authenticated && shouldCloudSync("push")) {
await putCloudSettings();
- delete localStorage.Vencord_settingsDirty;
}
}, 60_000);
diff --git a/src/api/SettingsSync/cloudSync.ts b/src/api/SettingsSync/cloudSync.ts
index 3784e3e5..488c3635 100644
--- a/src/api/SettingsSync/cloudSync.ts
+++ b/src/api/SettingsSync/cloudSync.ts
@@ -6,6 +6,7 @@
import { showNotification } from "@api/Notifications";
import { PlainSettings, Settings } from "@api/Settings";
+import { localStorage } from "@utils/localStorage";
import { Logger } from "@utils/Logger";
import { relaunch } from "@utils/native";
import { deflateSync, inflateSync } from "fflate";
@@ -15,6 +16,12 @@ import { exportSettings, importSettings } from "./offline";
const logger = new Logger("SettingsSync:Cloud", "#39b7e0");
+export function shouldCloudSync(direction: "push" | "pull") {
+ const localDirection = localStorage.Vencord_cloudSyncDirection;
+
+ return localDirection === direction || localDirection === "both";
+}
+
export async function putCloudSettings(manual?: boolean) {
const settings = await exportSettings({ minify: true });
@@ -53,6 +60,8 @@ export async function putCloudSettings(manual?: boolean) {
noPersist: true,
});
}
+
+ delete localStorage.Vencord_settingsDirty;
} catch (e: any) {
logger.error("Failed to sync up", e);
showNotification({
@@ -141,6 +150,8 @@ export async function getCloudSettings(shouldNotify = true, force = false) {
noPersist: true
});
+ delete localStorage.Vencord_settingsDirty;
+
return true;
} catch (e: any) {
logger.error("Failed to sync down", e);
diff --git a/src/components/Button.css b/src/components/Button.css
index 6f70510b..1e8d4183 100644
--- a/src/components/Button.css
+++ b/src/components/Button.css
@@ -124,11 +124,11 @@
}
.vc-btn-positive {
- background-color: var(--redesign-button-positive-background);
+ background-color: var(--redesign-button-positive-background, var(--green-430));
color: var(--white);
&:hover {
- background-color: var(--redesign-button-positive-pressed-background);
+ background-color: var(--redesign-button-positive-pressed-background, var(--green-460));
}
}
diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx
index eede9559..877eb89d 100644
--- a/src/components/Icons.tsx
+++ b/src/components/Icons.tsx
@@ -542,3 +542,31 @@ export function VesktopSettingsIcon(props: IconProps) {
);
}
+
+export function CloudDownloadIcon(props: IconProps) {
+ return (
+
+
+
+ );
+}
+
+export function CloudUploadIcon(props: IconProps) {
+ return (
+
+
+
+ );
+}
diff --git a/src/components/settings/tabs/styles.css b/src/components/settings/tabs/styles.css
index 119dfac1..edf5fe8b 100644
--- a/src/components/settings/tabs/styles.css
+++ b/src/components/settings/tabs/styles.css
@@ -43,6 +43,7 @@
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1em;
+ margin-top: 16px;
}
.vc-cloud-erase-data-danger-btn {
@@ -50,6 +51,16 @@
background-color: var(--redesign-button-danger-background);
}
+.vc-cloud-icon-with-button {
+ display: flex;
+ gap: 0.5em;
+ padding-inline: 0.5em 1em;
+}
+
+.vc-cloud-button-icon {
+ height: 1.25em;
+}
+
.vc-settings-modal {
padding: 1.5em !important;
}
diff --git a/src/components/settings/tabs/sync/CloudTab.tsx b/src/components/settings/tabs/sync/CloudTab.tsx
index bab67a7f..0f01dbd1 100644
--- a/src/components/settings/tabs/sync/CloudTab.tsx
+++ b/src/components/settings/tabs/sync/CloudTab.tsx
@@ -19,15 +19,23 @@
import { useSettings } from "@api/Settings";
import { authorizeCloud, deauthorizeCloud } from "@api/SettingsSync/cloudSetup";
import { deleteCloudSettings, eraseAllCloudData, getCloudSettings, putCloudSettings } from "@api/SettingsSync/cloudSync";
+import { BaseText } from "@components/BaseText";
+import { Button, ButtonProps } 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 { CloudDownloadIcon, CloudUploadIcon, DeleteIcon, RestartIcon } from "@components/Icons";
import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
+import { localStorage } from "@utils/localStorage";
import { Margins } from "@utils/margins";
-import { Alerts, Button, Forms, Tooltip } from "@webpack/common";
+import { classes } from "@utils/misc";
+import { IconComponent } from "@utils/types";
+import { Alerts, Select, Tooltip } from "@webpack/common";
function validateUrl(url: string) {
try {
@@ -38,131 +46,214 @@ function validateUrl(url: string) {
}
}
+const SectionHeading = ({ text }: { text: string; }) => (
+
+ {text}
+
+);
+
+function ButtonWithIcon({ children, Icon, className, ...buttonProps }: ButtonProps & { Icon: IconComponent; }) {
+ return (
+
+ );
+}
+
+function CloudSetupSection() {
+ const { cloud } = useSettings(["cloud.authenticated", "cloud.url"]);
+
+ return (
+
+
+
+
+ Vencord comes with a cloud integration that adds goodies like settings sync across devices.
+ It respects your privacy, and
+ the source code is AGPL 3.0 licensed so you
+ can host it yourself.
+
+ {
+ if (v)
+ authorizeCloud();
+ else
+ cloud.authenticated = v;
+ }}
+ />
+ Backend URL
+
+ Which backend to use when using cloud integrations.
+
+ {
+ cloud.url = v;
+ cloud.authenticated = false;
+ deauthorizeCloud();
+ }}
+ validate={validateUrl}
+ />
+
+
+ {
+ await deauthorizeCloud();
+ cloud.authenticated = false;
+ await authorizeCloud();
+ }}
+ Icon={RestartIcon}
+ >
+ Reauthorise
+
+
+
+ );
+}
+
function SettingsSyncSection() {
const { cloud } = useSettings(["cloud.authenticated", "cloud.settingsSync"]);
const sectionEnabled = cloud.authenticated && cloud.settingsSync;
return (
-
- Settings Sync
+
+
+
+ { cloud.settingsSync = v; }}
+ disabled={!cloud.authenticated}
+ hideBorder
+ />
-
- Synchronize your settings to the cloud. This allows easy synchronization across multiple devices with
- minimal effort.
-
- { cloud.settingsSync = v; }}
- disabled={!cloud.authenticated}
- />
-
-
-
- {({ onMouseLeave, onMouseEnter }) => (
-
- )}
-
-
+
+
+ putCloudSettings(true)}
+ Icon={CloudUploadIcon}
+ >
+ Upload Settings
+
+
+ {({ onMouseLeave, onMouseEnter }) => (
+ getCloudSettings(true, true)}
+ Icon={CloudDownloadIcon}
+ >
+ Download Settings
+
+ )}
+
+
+
+
+ );
+}
+
+function ResetSection() {
+ const { authenticated, settingsSync } = useSettings(["cloud.authenticated", "cloud.settingsSync"]).cloud;
+
+ return (
+
+
+
+
+ deleteCloudSettings()}
+ Icon={DeleteIcon}
>
- Delete Cloud Settings
-
-
+ Delete Settings from Cloud
+
+ Alerts.show({
+ title: "Are you sure?",
+ body: "Once your data is erased, we cannot recover it. There's no going back!",
+ onConfirm: eraseAllCloudData,
+ confirmText: "Erase it!",
+ confirmColor: "vc-cloud-erase-data-danger-btn",
+ cancelText: "Nevermind"
+ })}
+ Icon={DeleteIcon}
+ >
+ Delete your Cloud Account
+
+
);
}
function CloudTab() {
- const settings = useSettings(["cloud.authenticated", "cloud.url"]);
-
return (
-
-
- Vencord comes with a cloud integration that adds goodies like settings sync across devices.
- It respects your privacy, and
- the source code is AGPL 3.0 licensed so you
- can host it yourself.
-
- {
- 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;
- deauthorizeCloud();
- }}
- validate={validateUrl}
- />
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
);
}