Add the possibility to activate and deactivate mods

This commit is contained in:
2026-03-01 20:17:08 +01:00
parent 0e895fe1fb
commit 380b9a4604
5 changed files with 122 additions and 58 deletions

114
main.js
View File

@@ -38,7 +38,7 @@ let onlineCachedModList;
let onlineTotalModsCount; let onlineTotalModsCount;
const bepinexFiles = [".doorstop_version", "changelog.txt", "doorstop_config.ini", "winhttp.dll"]; const bepinexFiles = [".doorstop_version", "changelog.txt", "doorstop_config.ini", "winhttp.dll"];
let bepinexVersion; let bepinexVersion = bepinexStore.get("bepinex-version");
let bepinexBackupVersion; let bepinexBackupVersion;
let mainWindow; let mainWindow;
@@ -120,6 +120,7 @@ app.on("open-url", (event, url) => {
ipcMain.handle("save-path", (event, path) => { ipcMain.handle("save-path", (event, path) => {
saveSilksongPath(path); saveSilksongPath(path);
}); });
function saveSilksongPath(path) { function saveSilksongPath(path) {
store.set("silksong-path", path); store.set("silksong-path", path);
} }
@@ -208,6 +209,7 @@ async function saveModInfo(modId, suppr = false) {
} }
const modInfo = onlineCachedModList.find((mod) => mod.modId == modId); const modInfo = onlineCachedModList.find((mod) => mod.modId == modId);
modInfo.activated = true;
installedModsStore.set(String(modId), modInfo); installedModsStore.set(String(modId), modInfo);
} }
@@ -310,9 +312,7 @@ async function installBepinex() {
saveBepinexVersion(release.tag_name); saveBepinexVersion(release.tag_name);
} }
if (await fileExists(modSavePath)) { checkInstalledMods();
await fs.cp(modSavePath, path.join(silksongPath, "BepInEx", "plugins"), { recursive: true });
}
} }
ipcMain.handle("install-bepinex", async () => { ipcMain.handle("install-bepinex", async () => {
@@ -440,12 +440,12 @@ ipcMain.handle("get-mods", async (event, type) => {
if (!installedCachedModList) { if (!installedCachedModList) {
await searchInstalledMods(""); await searchInstalledMods("");
} }
return { modsInfo: installedCachedModList, installedTotalCount: installedTotalModsCount }; return { installedModsInfo: installedCachedModList, installedTotalCount: installedTotalModsCount };
} else if (type == "mods-online") { } else if (type == "mods-online") {
if (!onlineCachedModList) { if (!onlineCachedModList) {
await searchNexusMods(""); await searchNexusMods("");
} }
return { mods: onlineCachedModList, onlineTotalCount: onlineTotalModsCount }; return { onlineModsInfo: onlineCachedModList, onlineTotalCount: onlineTotalModsCount };
} }
}); });
@@ -511,38 +511,6 @@ async function startDownload(modId, fileId, key, expires) {
installedCachedModList = undefined; installedCachedModList = undefined;
} }
async function checkInstalledMods() {
const bepinexFolderPath = path.join(loadSilksongPath(), "BepInEx");
for (const [key, modInfo] of Object.entries(installedModsStore.store)) {
modInfo.modId = String(modInfo.modId);
if (!(await fileExists(path.join(modSavePath, modInfo.modId)))) {
saveModInfo(key, true);
await fs.rm(path.join(bepinexFolderPath, "plugins", modInfo.modId), { recursive: true });
}
}
}
ipcMain.handle("uninstall-mod", async (event, modId) => {
modId = String(modId);
const BepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
const modPath = path.join(BepinexPluginsPath, modId);
if (await fileExists(path.join(modSavePath, modId))) {
await fs.rm(path.join(modSavePath, modId), { recursive: true });
}
if (await fileExists(modPath)) {
await fs.rm(modPath, { recursive: true });
}
for (let i = 0; i < installedCachedModList.length; i++) {
if (installedCachedModList[i].modId == modId) {
installedCachedModList.splice(i, 1);
}
}
saveModInfo(modId, true);
});
ipcMain.handle("search-nexus-mods", async (event, keywords, offset, count, sortFilter, sortOrder) => { ipcMain.handle("search-nexus-mods", async (event, keywords, offset, count, sortFilter, sortOrder) => {
await searchNexusMods(keywords, offset, count, sortFilter, sortOrder); await searchNexusMods(keywords, offset, count, sortFilter, sortOrder);
}); });
@@ -608,6 +576,9 @@ async function searchNexusMods(keywords, offset = 0, count = 10, sortFilter = "d
onlineTotalModsCount = data.mods.totalCount; onlineTotalModsCount = data.mods.totalCount;
} }
//////////////////////////////////////////////////////
//////////////////////// MODS ////////////////////////
ipcMain.handle("search-installed-mods", async (event, keywords, offset, count, sortFilter, sortOrder) => { ipcMain.handle("search-installed-mods", async (event, keywords, offset, count, sortFilter, sortOrder) => {
await searchInstalledMods(keywords, offset, count, sortFilter, sortOrder); await searchInstalledMods(keywords, offset, count, sortFilter, sortOrder);
}); });
@@ -635,6 +606,73 @@ async function searchInstalledMods(keywords, offset = 0, count = 10, sortFilter
installedCachedModList = modsInfoSorted.slice(offset, offset + count); installedCachedModList = modsInfoSorted.slice(offset, offset + count);
} }
async function checkInstalledMods() {
const bepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
for (const [key, modInfo] of Object.entries(installedModsStore.store)) {
modInfo.modId = String(modInfo.modId);
if (!(await fileExists(path.join(modSavePath, modInfo.modId)))) {
saveModInfo(key, true);
if (await fileExists(path.join(bepinexPluginsPath, modInfo.modId))) {
await fs.rm(path.join(bepinexPluginsPath, modInfo.modId), { recursive: true });
}
continue;
}
if (modInfo.activated) {
await fs.cp(path.join(modSavePath, modInfo.modId), path.join(bepinexPluginsPath, modInfo.modId), { recursive: true });
}
}
}
ipcMain.handle("uninstall-mod", async (event, modId) => {
modId = String(modId);
const BepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
const modPath = path.join(BepinexPluginsPath, modId);
if (await fileExists(path.join(modSavePath, modId))) {
await fs.rm(path.join(modSavePath, modId), { recursive: true });
}
if (await fileExists(modPath)) {
await fs.rm(modPath, { recursive: true });
}
for (let i = 0; i < installedCachedModList.length; i++) {
if (installedCachedModList[i].modId == modId) {
installedCachedModList.splice(i, 1);
}
}
saveModInfo(modId, true);
});
ipcMain.handle("activate-mod", async (event, modId) => {
const BepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
if (!installedModsStore.get(`${modId}.activated`)) {
installedModsStore.set(`${modId}.activated`, true);
if (bepinexVersion) {
if (!(await fileExists(path.join(BepinexPluginsPath, String(modId))))) {
await fs.cp(path.join(modSavePath, String(modId)), path.join(BepinexPluginsPath, String(modId)), { recursive: true });
}
}
}
});
ipcMain.handle("deactivate-mod", async (event, modId) => {
const BepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
if (installedModsStore.get(`${modId}.activated`)) {
installedModsStore.set(`${modId}.activated`, false);
if (bepinexVersion) {
if (await fileExists(path.join(BepinexPluginsPath, String(modId)))) {
await fs.rm(path.join(BepinexPluginsPath, String(modId)), { recursive: true });
}
}
}
});
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
//////////////////// UNCATEGORIZE //////////////////// //////////////////// UNCATEGORIZE ////////////////////

View File

@@ -45,9 +45,14 @@ contextBridge.exposeInMainWorld("bepinex", {
contextBridge.exposeInMainWorld("nexus", { contextBridge.exposeInMainWorld("nexus", {
verifyAPI: () => ipcRenderer.invoke("verify-nexus-api"), verifyAPI: () => ipcRenderer.invoke("verify-nexus-api"),
getMods: (type) => ipcRenderer.invoke("get-mods", type),
download: (link) => ipcRenderer.invoke("open-download", link), download: (link) => ipcRenderer.invoke("open-download", link),
uninstall: (modId) => ipcRenderer.invoke("uninstall-mod", modId),
search: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-nexus-mods", keywords, offset, count, sortFilter, sortOrder), search: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-nexus-mods", keywords, offset, count, sortFilter, sortOrder),
searchInstalled: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-installed-mods", keywords, offset, count, sortFilter, sortOrder), });
contextBridge.exposeInMainWorld("mods", {
searchInstalled: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-installed-mods", keywords, offset, count, sortFilter, sortOrder),
uninstall: (modId) => ipcRenderer.invoke("uninstall-mod", modId),
getMods: (type) => ipcRenderer.invoke("get-mods", type),
activateMods: (modId) => ipcRenderer.invoke("activate-mod", modId),
deactivateMods: (modId) => ipcRenderer.invoke("deactivate-mod", modId),
}); });

View File

@@ -170,6 +170,11 @@
<div class="horizontal-div"> <div class="horizontal-div">
<a href="www.nexusmods.com/hollowknightsilksong/mods" class="default-button" id="uninstall-mod-button">Uninstall</a> <a href="www.nexusmods.com/hollowknightsilksong/mods" class="default-button" id="uninstall-mod-button">Uninstall</a>
<a href="www.nexusmods.com/hollowknightsilksong/mods" class="default-button" id="external-link">Website</a> <a href="www.nexusmods.com/hollowknightsilksong/mods" class="default-button" id="external-link">Website</a>
<p>Activated:</p>
<label class="checkbox-container">
<input type="checkbox" name="activated-mod" id="activated-mod" />
<span class="checkmark"></span>
</label>
</div> </div>
</div> </div>
@@ -221,7 +226,7 @@
<li id="Steel" onclick="changeTheme('Steel')">Steel</li> <li id="Steel" onclick="changeTheme('Steel')">Steel</li>
</div> </div>
</div> </div>
<label class="lace-pin-checkbox-container"> <label class="checkbox-container">
<input type="checkbox" name="lace-pin" id="lace-pin" /> <input type="checkbox" name="lace-pin" id="lace-pin" />
<span class="checkmark"></span> <span class="checkmark"></span>
Lace Pin Lace Pin

View File

@@ -98,14 +98,15 @@ async function navigate(page) {
toggleSelectedListButton("sort-menu", installedSortFilter); toggleSelectedListButton("sort-menu", installedSortFilter);
setSortOrderButton(); setSortOrderButton();
const { modsInfo, installedTotalCount } = await nexus.getMods(page); const { installedModsInfo, installedTotalCount } = await mods.getMods(page);
installedModsTotalCount = installedTotalCount; installedModsTotalCount = installedTotalCount;
if (modsInfo == []) { if (installedModsInfo == []) {
break; break;
} }
for (const modInfo of modsInfo) { for (const modInfo of installedModsInfo) {
const installedModTemplateCopy = installedModTemplate.content.cloneNode(true); const installedModTemplateCopy = installedModTemplate.content.cloneNode(true);
if (modInfo.name) { if (modInfo.name) {
const modTitleText = installedModTemplateCopy.getElementById("mod-title"); const modTitleText = installedModTemplateCopy.getElementById("mod-title");
modTitleText.innerText = modInfo.name; modTitleText.innerText = modInfo.name;
@@ -143,6 +144,21 @@ async function navigate(page) {
modVersionText.innerText = `V${modInfo.version} last updated on ${modInfo.updatedAt.slice(0, 10)}`; modVersionText.innerText = `V${modInfo.version} last updated on ${modInfo.updatedAt.slice(0, 10)}`;
} }
const isActivatedCheckbox = installedModTemplateCopy.getElementById("activated-mod");
if (modInfo.activated) {
isActivatedCheckbox.checked = true;
}
isActivatedCheckbox.addEventListener("change", async function () {
if (this.checked) {
mods.activateMods(modInfo.modId);
searchInstalledMods();
} else {
mods.deactivateMods(modInfo.modId);
searchInstalledMods();
}
});
const modUrl = `https://www.nexusmods.com/hollowknightsilksong/mods/${modInfo.modId}`; const modUrl = `https://www.nexusmods.com/hollowknightsilksong/mods/${modInfo.modId}`;
const modLinkButton = installedModTemplateCopy.getElementById("external-link"); const modLinkButton = installedModTemplateCopy.getElementById("external-link");
@@ -156,7 +172,7 @@ async function navigate(page) {
const uninstallModButton = installedModTemplateCopy.getElementById("uninstall-mod-button"); const uninstallModButton = installedModTemplateCopy.getElementById("uninstall-mod-button");
uninstallModButton.addEventListener("click", async function (event) { uninstallModButton.addEventListener("click", async function (event) {
event.preventDefault(); event.preventDefault();
await nexus.uninstall(modInfo.modId); await mods.uninstall(modInfo.modId);
navigate("refresh"); navigate("refresh");
}); });
@@ -182,12 +198,12 @@ async function navigate(page) {
toggleSelectedListButton("sort-menu", onlineSortFilter); toggleSelectedListButton("sort-menu", onlineSortFilter);
setSortOrderButton(); setSortOrderButton();
const { mods, onlineTotalCount } = await nexus.getMods(page); const { onlineModsInfo, onlineTotalCount } = await mods.getMods(page);
onlineModsTotalCount = onlineTotalCount; onlineModsTotalCount = onlineTotalCount;
if (mods == undefined) { if (onlineModsInfo == undefined) {
break; break;
} }
for (const mod of mods) { for (const mod of onlineModsInfo) {
if (mod.name == undefined) { if (mod.name == undefined) {
continue; continue;
} }
@@ -448,7 +464,7 @@ async function setBepinexVersion() {
async function searchInstalledMods() { async function searchInstalledMods() {
const searchInput = document.getElementById("search-input"); const searchInput = document.getElementById("search-input");
searchValueInstalled = searchInput.value; searchValueInstalled = searchInput.value;
await nexus.searchInstalled(searchValueInstalled, installedOffset, installedModsCount, installedSortFilter, installedSortOrder); await mods.searchInstalled(searchValueInstalled, installedOffset, installedModsCount, installedSortFilter, installedSortOrder);
await navigate("refresh"); await navigate("refresh");
} }

View File

@@ -347,7 +347,7 @@ body {
background: var(--primary-color); background: var(--primary-color);
} }
.lace-pin-checkbox-container { .checkbox-container {
display: flex; display: flex;
align-items: center; align-items: center;
position: relative; position: relative;
@@ -356,13 +356,13 @@ body {
user-select: none; user-select: none;
} }
.lace-pin-checkbox-container input { .checkbox-container input {
opacity: 0; opacity: 0;
height: 0; height: 0;
width: 0; width: 0;
} }
.lace-pin-checkbox-container .checkmark { .checkbox-container .checkmark {
position: absolute; position: absolute;
left: 0; left: 0;
height: 30px; height: 30px;
@@ -373,27 +373,27 @@ body {
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.lace-pin-checkbox-container:hover .checkmark { .checkbox-container:hover .checkmark {
background: var(--darker-transparent-black); background: var(--darker-transparent-black);
border-color: var(--primary-color); border-color: var(--primary-color);
} }
.lace-pin-checkbox-container input:checked ~ .checkmark { .checkbox-container input:checked ~ .checkmark {
background: var(--primary-color); background: var(--primary-color);
border-color: var(--primary-color); border-color: var(--primary-color);
} }
.lace-pin-checkbox-container .checkmark:after { .checkbox-container .checkmark:after {
content: ""; content: "";
position: absolute; position: absolute;
display: none; display: none;
} }
.lace-pin-checkbox-container input:checked ~ .checkmark:after { .checkbox-container input:checked ~ .checkmark:after {
display: block; display: block;
} }
.lace-pin-checkbox-container .checkmark:after { .checkbox-container .checkmark:after {
left: 8px; left: 8px;
width: 10px; width: 10px;
height: 20px; height: 20px;