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 }) => ( - - )} - -