From b5bc88c7d441f8d24df70419d7e8e77eaa7f284e Mon Sep 17 00:00:00 2001 From: Ven Date: Mon, 21 Nov 2022 19:25:40 +0100 Subject: [PATCH] Settings export/import (#235) --- src/components/Settings.tsx | 18 ++++++ src/utils/settingsSync.ts | 123 ++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 src/utils/settingsSync.ts diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index fc2590115..e2abff2e3 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -20,6 +20,7 @@ import { useSettings } from "../api/settings"; import { ChangeList } from "../utils/ChangeList"; import IpcEvents from "../utils/IpcEvents"; import { useAwaiter } from "../utils/misc"; +import { downloadSettingsBackup, uploadSettingsBackup } from "../utils/settingsSync"; import { Alerts, Button, Card, Forms, Margins, Parser, React, Switch } from "../webpack/common"; import DonateButton from "./DonateButton"; import ErrorBoundary from "./ErrorBoundary"; @@ -136,6 +137,23 @@ export default ErrorBoundary.wrap(function Settings() { > Get notified about new Updates } + + + Settings Sync + + + + ); }, { diff --git a/src/utils/settingsSync.ts b/src/utils/settingsSync.ts new file mode 100644 index 000000000..ecf66652a --- /dev/null +++ b/src/utils/settingsSync.ts @@ -0,0 +1,123 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +import { Toasts } from "../webpack/common"; +import IpcEvents from "./IpcEvents"; +import Logger from "./Logger"; + +export async function importSettings(data: string) { + try { + var parsed = JSON.parse(data); + } catch (err) { + console.log(data); + throw new Error("Failed to parse JSON: " + String(err)); + } + + if ("settings" in parsed && "quickCss" in parsed) { + await VencordNative.ipc.invoke(IpcEvents.SET_SETTINGS, JSON.stringify(parsed.settings, null, 4)); + await VencordNative.ipc.invoke(IpcEvents.SET_QUICK_CSS, parsed.quickCss); + } else + throw new Error("Invalid Settings. Is this even a Vencord Settings file?"); +} + +export async function exportSettings() { + const settings = JSON.parse(VencordNative.ipc.sendSync(IpcEvents.GET_SETTINGS)); + const quickCss = await VencordNative.ipc.invoke(IpcEvents.GET_QUICK_CSS); + return JSON.stringify({ settings, quickCss }, null, 4); +} + +export async function downloadSettingsBackup() { + const filename = "vencord-settings-backup.json"; + const backup = await exportSettings(); + const data = new TextEncoder().encode(backup); + + if (IS_WEB) { + const file = new File([data], filename, { type: "application/json" }); + const a = document.createElement("a"); + a.href = URL.createObjectURL(file); + a.download = filename; + + document.body.appendChild(a); + a.click(); + setImmediate(() => { + URL.revokeObjectURL(a.href); + document.body.removeChild(a); + }); + } else { + DiscordNative.fileManager.saveWithDialog(data, filename); + } +} + +const toastSuccess = () => Toasts.show({ + type: Toasts.Type.SUCCESS, + message: "Settings successfully imported. Restart to apply changes!", + id: Toasts.genId() +}); + +const toastFailure = (err: any) => Toasts.show({ + type: Toasts.Type.FAILURE, + message: `Failed to import settings: ${String(err)}`, + id: Toasts.genId() +}); + +export async function uploadSettingsBackup(showToast = true): Promise { + if (IS_WEB) { + const input = document.createElement("input"); + input.type = "file"; + input.style.display = "none"; + input.accept = "application/json"; + input.onchange = async () => { + const file = input.files?.[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = async () => { + try { + await importSettings(reader.result as string); + if (showToast) toastSuccess(); + } catch (err) { + new Logger("SettingsSync").error(err); + if (showToast) toastFailure(err); + } + }; + reader.readAsText(file); + }; + + document.body.appendChild(input); + input.click(); + setImmediate(() => document.body.removeChild(input)); + } else { + const [file] = await DiscordNative.fileManager.openFiles({ + filters: [ + { name: "Vencord Settings Backup", extensions: ["json"] }, + { name: "all", extensions: ["*"] } + ] + }); + + if (file) { + try { + console.log(file); + await importSettings(new TextDecoder().decode(file.data)); + if (showToast) toastSuccess(); + } catch (err) { + new Logger("SettingsSync").error(err); + if (showToast) toastFailure(err); + } + } + } +}