mirror of
https://github.com/smartfrigde/armcord.git
synced 2024-08-14 23:56:58 +00:00
Add theme manager
This commit is contained in:
parent
9ff515b07b
commit
a07546f28e
12 changed files with 496 additions and 11 deletions
|
@ -29,3 +29,9 @@
|
|||
[class|="listItem"]:has(+ [class|="listItem"] [data-list-item-id="guildsnav___app-download-button"]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div#themes:after,
|
||||
div#armcord:after {
|
||||
content: url("https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/ac_white_plug16x.png");
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -36,7 +36,7 @@
|
|||
.titlebar #window-controls-container #quit {
|
||||
float: left;
|
||||
height: 100%;
|
||||
width: 33.1%;
|
||||
width: 33.3%;
|
||||
text-align: center;
|
||||
color: var(--interactive-normal);
|
||||
cursor: default;
|
||||
|
|
|
@ -16,6 +16,7 @@ import {customTitlebar} from "./main";
|
|||
import {createSettingsWindow} from "./settings/main";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import {createTManagerWindow} from "./themeManager/main";
|
||||
export function registerIpc(): void {
|
||||
ipcMain.on("get-app-path", (event) => {
|
||||
event.reply("app-path", app.getAppPath());
|
||||
|
@ -142,6 +143,9 @@ export function registerIpc(): void {
|
|||
ipcMain.on("openSettingsWindow", () => {
|
||||
createSettingsWindow();
|
||||
});
|
||||
ipcMain.on("openManagerWindow", () => {
|
||||
createTManagerWindow();
|
||||
});
|
||||
ipcMain.on("setting-armcordCSP", async (event) => {
|
||||
if (await getConfig("armcordCSP")) {
|
||||
event.returnValue = true;
|
||||
|
|
|
@ -59,6 +59,13 @@ export async function setMenu(): Promise<void> {
|
|||
createSettingsWindow();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Fullscreen",
|
||||
accelerator: "F11",
|
||||
click() {
|
||||
mainWindow.fullScreen = !mainWindow.fullScreen;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Reload",
|
||||
accelerator: "CmdOrCtrl+R",
|
||||
|
|
9
src/preload/optimizer.ts
Normal file
9
src/preload/optimizer.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
const optimize = (orig: Function) =>
|
||||
function (this: any, ...args: any[]) {
|
||||
if (typeof args[0]?.className === "string" && args[0].className.indexOf("activity") !== -1)
|
||||
return setTimeout(() => orig.apply(this, args), 100);
|
||||
|
||||
return orig.apply(this, args);
|
||||
};
|
||||
|
||||
Element.prototype.removeChild = optimize(Element.prototype.removeChild);
|
|
@ -1,5 +1,6 @@
|
|||
import "./bridge";
|
||||
import "./patch";
|
||||
import "./optimizer";
|
||||
|
||||
import {ipcRenderer} from "electron";
|
||||
import * as fs from "fs";
|
||||
|
@ -78,12 +79,33 @@ if (window.location.href.indexOf("splash.html") > -1) {
|
|||
|
||||
// Settings info version injection
|
||||
setInterval(() => {
|
||||
const host = document.querySelector("nav > [class|=side] [class|=info]");
|
||||
if (!host || host.querySelector("#ac-ver")) return;
|
||||
const el = host.firstChild!.cloneNode() as HTMLSpanElement;
|
||||
el.id = "ac-ver";
|
||||
const host = document.querySelector<HTMLDivElement>("nav > [class|=side] [class|=info]");
|
||||
if (!host || host.querySelector("#ac-ver")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = host.firstElementChild!.cloneNode() as HTMLSpanElement;
|
||||
el.id = "ac-ver";
|
||||
el.textContent = `ArmCord Version: ${version}`;
|
||||
el.onclick = () => ipcRenderer.send("openSettingsWindow");
|
||||
host.append(el);
|
||||
}, 2000);
|
||||
let advanced = document
|
||||
.querySelector('[class*="socialLinks-"]')!
|
||||
.parentElement!.querySelector(
|
||||
'[class*="header"] + [class*="item"] + [class*="item"] + [class*="item"] + [class*="item"] + [class*="item"] + [class*="item"] + [class*="item"] + [class*="item"] + [class*="item"]'
|
||||
);
|
||||
if (!advanced) return;
|
||||
if (advanced.nextSibling instanceof Element && advanced.nextSibling.className.includes("item")) {
|
||||
advanced = advanced.nextSibling;
|
||||
}
|
||||
const acSettings = advanced.cloneNode(true) as HTMLElement;
|
||||
const tManager = advanced.cloneNode(true) as HTMLElement;
|
||||
acSettings.textContent = "ArmCord";
|
||||
acSettings.id = "armcord";
|
||||
acSettings.onclick = () => ipcRenderer.send("openSettingsWindow");
|
||||
tManager.textContent = "Themes";
|
||||
tManager.id = "themes";
|
||||
tManager.onclick = () => ipcRenderer.send("openManagerWindow");
|
||||
advanced.insertAdjacentElement("afterend", acSettings);
|
||||
advanced.insertAdjacentElement("afterend", tManager);
|
||||
}, 1000);
|
||||
|
|
|
@ -50,6 +50,9 @@ export function createSettingsWindow(): void {
|
|||
fs.mkdirSync(themesFolder);
|
||||
console.log("Created missing theme folder");
|
||||
}
|
||||
if (!fs.existsSync(`${userDataPath}/disabled.txt`)) {
|
||||
fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), "");
|
||||
}
|
||||
settingsWindow.webContents.on("did-finish-load", () => {
|
||||
fs.readdirSync(themesFolder).forEach((file) => {
|
||||
try {
|
||||
|
|
181
src/themeManager/main.ts
Normal file
181
src/themeManager/main.ts
Normal file
|
@ -0,0 +1,181 @@
|
|||
import {BrowserWindow, app, ipcMain, shell, dialog} from "electron";
|
||||
import {sleep} from "../utils";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import {mainWindow} from "../window";
|
||||
let themeWindow: BrowserWindow;
|
||||
let instance = 0;
|
||||
interface ThemeManifest {
|
||||
name?: string;
|
||||
author?: string;
|
||||
description?: string;
|
||||
version?: string;
|
||||
invite?: string;
|
||||
authorId?: string;
|
||||
theme: string;
|
||||
authorLink?: string;
|
||||
donate?: string;
|
||||
patreon?: string;
|
||||
website?: string;
|
||||
source?: string;
|
||||
}
|
||||
function parseBDManifest(content: string) {
|
||||
const metaReg = /@([^ ]*) (.*)/g;
|
||||
if (!content.startsWith("/**")) {
|
||||
throw new Error("Not a manifest.");
|
||||
}
|
||||
let manifest: ThemeManifest = {theme: "src.css"};
|
||||
|
||||
let match;
|
||||
while ((match = metaReg.exec(content)) !== null) {
|
||||
let [_, key, value] = match;
|
||||
if (key === "import") break;
|
||||
|
||||
value = value.trim();
|
||||
|
||||
//console.log(key, value);
|
||||
|
||||
switch (key) {
|
||||
case "name":
|
||||
manifest.name = value;
|
||||
break;
|
||||
|
||||
case "description":
|
||||
manifest.description = value;
|
||||
break;
|
||||
|
||||
case "version":
|
||||
manifest.version = value;
|
||||
break;
|
||||
|
||||
case "author":
|
||||
manifest.author = value;
|
||||
break;
|
||||
|
||||
case "invite":
|
||||
manifest.invite = value;
|
||||
break;
|
||||
|
||||
case "authorId":
|
||||
manifest.authorId = value;
|
||||
break;
|
||||
|
||||
case "authorLink":
|
||||
manifest.authorLink = value;
|
||||
break;
|
||||
|
||||
case "donate":
|
||||
manifest.donate = value;
|
||||
break;
|
||||
|
||||
case "patreon":
|
||||
manifest.patreon = value;
|
||||
break;
|
||||
|
||||
case "website":
|
||||
manifest.website = value;
|
||||
break;
|
||||
|
||||
case "source":
|
||||
manifest.source = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return manifest;
|
||||
}
|
||||
const userDataPath = app.getPath("userData");
|
||||
const themesPath = path.join(userDataPath, "/themes/");
|
||||
export function createTManagerWindow(): void {
|
||||
console.log("Creating theme manager window.");
|
||||
instance += 1;
|
||||
if (instance > 1) {
|
||||
if (themeWindow) {
|
||||
themeWindow.show();
|
||||
themeWindow.restore();
|
||||
}
|
||||
} else {
|
||||
themeWindow = new BrowserWindow({
|
||||
width: 700,
|
||||
height: 600,
|
||||
title: `ArmCord Theme Manager`,
|
||||
darkTheme: true,
|
||||
frame: true,
|
||||
backgroundColor: "#2f3136",
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
preload: path.join(__dirname, "preload.js")
|
||||
}
|
||||
});
|
||||
async function managerLoadPage(): Promise<void> {
|
||||
themeWindow.loadFile(`${__dirname}/manager.html`);
|
||||
}
|
||||
const userDataPath = app.getPath("userData");
|
||||
const themesFolder = `${userDataPath}/themes/`;
|
||||
if (!fs.existsSync(themesFolder)) {
|
||||
fs.mkdirSync(themesFolder);
|
||||
console.log("Created missing theme folder");
|
||||
}
|
||||
if (!fs.existsSync(`${userDataPath}/disabled.txt`)) {
|
||||
fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), "");
|
||||
}
|
||||
ipcMain.on("openThemesFolder", async () => {
|
||||
shell.showItemInFolder(themesPath);
|
||||
await sleep(1000);
|
||||
});
|
||||
ipcMain.on("reloadMain", async () => {
|
||||
mainWindow.webContents.reload();
|
||||
});
|
||||
ipcMain.on("addToDisabled", async (_event, name: string) => {
|
||||
fs.appendFileSync(path.join(userDataPath, "/disabled.txt"), name + "\n");
|
||||
});
|
||||
ipcMain.on("disabled", async (e) => {
|
||||
e.returnValue = fs.readFileSync(path.join(userDataPath, "/disabled.txt")).toString();
|
||||
});
|
||||
ipcMain.on("removeFromDisabled", async (_event, name: string) => {
|
||||
var e = await fs.readFileSync(path.join(userDataPath, "/disabled.txt")).toString();
|
||||
fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), e.replace(name + "\n", ""));
|
||||
});
|
||||
ipcMain.on("installBDTheme", async (_event, link: string) => {
|
||||
try {
|
||||
var code = await (await fetch(link)).text();
|
||||
var manifest = parseBDManifest(code);
|
||||
var themePath = path.join(themesFolder, manifest.name?.replace(" ", "-") + "-BD");
|
||||
if (!fs.existsSync(themePath)) {
|
||||
fs.mkdirSync(themePath);
|
||||
console.log(`Created ${manifest.name} folder`);
|
||||
}
|
||||
fs.writeFileSync(path.join(themePath, "manifest.json"), JSON.stringify(manifest));
|
||||
fs.writeFileSync(path.join(themePath, "src.css"), code);
|
||||
dialog.showMessageBoxSync({
|
||||
type: "info",
|
||||
title: "BD Theme import success",
|
||||
message: "Successfully imported theme from link."
|
||||
});
|
||||
themeWindow.webContents.reload();
|
||||
mainWindow.webContents.reload();
|
||||
} catch (e) {
|
||||
dialog.showErrorBox(
|
||||
"BD Theme import fail",
|
||||
"Failed to import theme from link. Please make sure that it's a valid BetterDiscord Theme."
|
||||
);
|
||||
}
|
||||
});
|
||||
themeWindow.webContents.on("did-finish-load", () => {
|
||||
fs.readdirSync(themesFolder).forEach((file) => {
|
||||
try {
|
||||
const manifest = fs.readFileSync(`${themesFolder}/${file}/manifest.json`, "utf8");
|
||||
console.log(manifest);
|
||||
themeWindow.webContents.send("themeManifest", manifest);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
managerLoadPage();
|
||||
themeWindow.on("close", () => {
|
||||
instance = 0;
|
||||
});
|
||||
}
|
||||
}
|
198
src/themeManager/manager.html
Normal file
198
src/themeManager/manager.html
Normal file
|
@ -0,0 +1,198 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
:root {
|
||||
--background-secondary: #2f3136;
|
||||
--background-secondary-alt: #292b2f;
|
||||
--background-floating: #18191c;
|
||||
--background-modifier-hover: rgba(106, 116, 128, 0.16);
|
||||
--brand-experiment: #7289da;
|
||||
--brand-experiment-560: #5c6fb1;
|
||||
--brand-experiment-600: #4e5d94;
|
||||
--interactive-normal: #b9bbbe;
|
||||
--interactive-hover: #dcddde;
|
||||
--text-muted: #72767d;
|
||||
--font-primary: "Whitney";
|
||||
--header-primary: #fff;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 1rem;
|
||||
|
||||
background: var(--background-secondary);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Whitney;
|
||||
font-weight: 200;
|
||||
font-style: normal;
|
||||
src: url(https://armcord.xyz/whitney_400.woff) format("woff");
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: "Whitney", sans-serif;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--background-floating);
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
border-color: var(--background-floating);
|
||||
border-style: solid;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.cards {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-gap: 1rem;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
/* Screen larger than 600px? 2 column */
|
||||
@media (min-width: 600px) {
|
||||
.cards {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Screen larger than 900px? 3 columns */
|
||||
@media (min-width: 900px) {
|
||||
.cards {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.flex-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* switches */
|
||||
.tgl {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tgl::-moz-selection,
|
||||
.tgl:after::-moz-selection,
|
||||
.tgl:before::-moz-selection,
|
||||
.tgl *::-moz-selection,
|
||||
.tgl *:after::-moz-selection,
|
||||
.tgl *:before::-moz-selection,
|
||||
.tgl + .tgl-btn::-moz-selection {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.tgl::selection,
|
||||
.tgl:after::selection,
|
||||
.tgl:before::selection,
|
||||
.tgl *::selection,
|
||||
.tgl *:after::selection,
|
||||
.tgl *:before::selection,
|
||||
.tgl + .tgl-btn::selection {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.tgl + .tgl-btn {
|
||||
outline: 0;
|
||||
display: block;
|
||||
width: 3em;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tgl + .tgl-btn:after,
|
||||
.tgl + .tgl-btn:before {
|
||||
position: relative;
|
||||
display: block;
|
||||
content: "";
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tgl + .tgl-btn:after {
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
.tgl + .tgl-btn:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tgl:checked + .tgl-btn:after {
|
||||
left: 56%;
|
||||
}
|
||||
|
||||
.tgl-light + .tgl-btn {
|
||||
background: var(--text-muted);
|
||||
border-radius: 25px;
|
||||
padding: 4px;
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
|
||||
.tgl-light + .tgl-btn:after {
|
||||
border-radius: 50px;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: rgb(255, 255, 255);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.tgl-light:checked + .tgl-btn {
|
||||
background: var(--brand-experiment);
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
margin: 8px 0;
|
||||
display: inline-block;
|
||||
border: 1px solid #72767d;
|
||||
background-color: #46484d;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#download {
|
||||
margin-top: 3px;
|
||||
height: 50px;
|
||||
vertical-align: middle;
|
||||
text-align: right;
|
||||
z-index: 99;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="flex-box">
|
||||
<input
|
||||
type="text"
|
||||
id="themeLink"
|
||||
name="themeLink"
|
||||
title="BetterDiscord theme format"
|
||||
placeholder="https://raw.githubusercontent.com/... [.css]"
|
||||
/>
|
||||
<img id="download" src="https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/Download.png" />
|
||||
</div>
|
||||
<br />
|
||||
<div class="cards" id="cardBox"></div>
|
||||
</body>
|
||||
</html>
|
43
src/themeManager/preload.ts
Normal file
43
src/themeManager/preload.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import {ipcRenderer} from "electron";
|
||||
import {sleep} from "../utils";
|
||||
ipcRenderer.on("themeManifest", (_event, json) => {
|
||||
var manifest = JSON.parse(json);
|
||||
console.log(manifest);
|
||||
sleep(1000);
|
||||
var e = document.getElementById("cardBox");
|
||||
|
||||
e?.insertAdjacentHTML(
|
||||
"beforeend",
|
||||
`
|
||||
<div class="card">
|
||||
<div class="flex-box">
|
||||
<h3>${manifest.name}</h3>
|
||||
<input id="${manifest.name.replace(" ", "-")}" class="tgl tgl-light left" type="checkbox" />
|
||||
<label class="tgl-btn left" for="${manifest.name.replace(" ", "-")}"></label>
|
||||
</div>
|
||||
<p>${manifest.description}</p>
|
||||
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
if (!ipcRenderer.sendSync("disabled").includes(manifest.name.replace(" ", "-"))) {
|
||||
(<HTMLInputElement>document.getElementById(manifest.name.replace(" ", "-"))).checked = true;
|
||||
}
|
||||
(<HTMLInputElement>document.getElementById(manifest.name.replace(" ", "-")))!.addEventListener(
|
||||
"input",
|
||||
function (evt) {
|
||||
ipcRenderer.send("reloadMain");
|
||||
if (this.checked) {
|
||||
ipcRenderer.send("removeFromDisabled", manifest.name.replace(" ", "-"));
|
||||
} else {
|
||||
ipcRenderer.send("addToDisabled", manifest.name.replace(" ", "-"));
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.getElementById("download")!.addEventListener("click", () => {
|
||||
ipcRenderer.send("installBDTheme", (<HTMLInputElement>document.getElementById("themeLink"))!.value);
|
||||
});
|
||||
});
|
|
@ -179,16 +179,28 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
|||
fs.mkdirSync(themesFolder);
|
||||
console.log("Created missing theme folder");
|
||||
}
|
||||
if (!fs.existsSync(`${userDataPath}/disabled.txt`)) {
|
||||
fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), "");
|
||||
}
|
||||
mainWindow.webContents.on("did-finish-load", () => {
|
||||
fs.readdirSync(themesFolder).forEach((file) => {
|
||||
try {
|
||||
const manifest = fs.readFileSync(`${themesFolder}/${file}/manifest.json`, "utf8");
|
||||
let themeFile = JSON.parse(manifest);
|
||||
if (
|
||||
fs
|
||||
.readFileSync(path.join(userDataPath, "/disabled.txt"))
|
||||
.toString()
|
||||
.includes(themeFile.name.replace(" ", "-"))
|
||||
) {
|
||||
console.log(`%cSkipped ${themeFile.name} made by ${themeFile.author}`, "color:red");
|
||||
} else {
|
||||
mainWindow.webContents.send(
|
||||
"themeLoader",
|
||||
fs.readFileSync(`${themesFolder}/${file}/${themeFile.theme}`, "utf-8")
|
||||
);
|
||||
console.log(`%cLoaded ${themeFile.name} made by ${themeFile.author}`, "color:red");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue