Convert main.js to ESM and prettify code with Prettier

This commit is contained in:
2026-02-17 12:22:48 +01:00
parent 332c897c49
commit 2e54d5b222
8 changed files with 737 additions and 747 deletions

424
main.js
View File

@@ -1,144 +1,141 @@
const { app, BrowserWindow , ipcMain, dialog, shell} = require('electron/main'); import { app, BrowserWindow, ipcMain, dialog, shell } from "electron";
const path = require('node:path'); import path from "path";
const Store = require('electron-store').default; import { fileURLToPath } from "url";
const fs = require('fs/promises'); import Store from "electron-store";
const { createWriteStream } = require('fs'); import fs from "fs/promises";
const { pipeline } = require('stream/promises'); import { createWriteStream } from "fs";
const extract = require('extract-zip'); import { pipeline } from "stream/promises";
const Nexus = require('@nexusmods/nexus-api').default; import extract from "extract-zip";
import NexusModule from "@nexusmods/nexus-api";
const Nexus = NexusModule.default;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const store = new Store(); const store = new Store();
const userSavePath = app.getPath('userData') const userSavePath = app.getPath("userData");
const dataPath = `${userSavePath}\\config.json` const dataPath = `${userSavePath}\\config.json`;
let silksongPath = store.get('silksong-path') let silksongPath = store.get("silksong-path");
let nexusAPI = store.get('nexus-api') let nexusAPI = store.get("nexus-api");
let nexus = undefined let nexus = undefined;
createNexus() createNexus();
let bepinexFolderPath = `${silksongPath}/BepInEx` let bepinexFolderPath = `${silksongPath}/BepInEx`;
let bepinexBackupPath = `${silksongPath}/BepInEx-Backup` let bepinexBackupPath = `${silksongPath}/BepInEx-Backup`;
const bepinexFiles = [ const bepinexFiles = [".doorstop_version", "changelog.txt", "doorstop_config.ini", "winhttp.dll"];
".doorstop_version",
"changelog.txt",
"doorstop_config.ini",
"winhttp.dll"
]
let bepinexVersion let bepinexVersion;
let bepinexBackupVersion let bepinexBackupVersion;
const bepinexStore = new Store({cwd: 'bepinex-version'}); const bepinexStore = new Store({ cwd: "bepinex-version" });
let mainWindow let mainWindow;
let htmlFile let htmlFile;
async function createWindow() { async function createWindow() {
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
width: 1280, width: 1280,
height: 720, height: 720,
webPreferences: { webPreferences: {
preload: path.join(__dirname, 'preload.js') preload: path.join(__dirname, "preload.js"),
} },
}) });
if(await fileExists(dataPath)) { if (await fileExists(dataPath)) {
htmlFile = "index.html" htmlFile = "index.html";
} } else {
else { htmlFile = "welcome.html";
htmlFile = "welcome.html"
} }
mainWindow.loadFile(`renderer/${htmlFile}`) mainWindow.loadFile(`renderer/${htmlFile}`);
} }
app.whenReady().then(() => { app.whenReady().then(() => {
createWindow() createWindow();
app.on('activate', () => { app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) { if (BrowserWindow.getAllWindows().length === 0) {
createWindow() createWindow();
} }
}) });
}) });
app.on('window-all-closed', () => { app.on("window-all-closed", () => {
if (process.platform !== 'darwin') { if (process.platform !== "darwin") {
app.quit() app.quit();
} }
}) });
//////////////////////////////////////////////////////
///////////////// SAVING AND LOADING ///////////////// ///////////////// SAVING AND LOADING /////////////////
ipcMain.handle('save-path', (event, path) => { ipcMain.handle("save-path", (event, path) => {
saveSilksongPath(path) saveSilksongPath(path);
}); });
function saveSilksongPath(path) { function saveSilksongPath(path) {
silksongPath = path; silksongPath = path;
bepinexFolderPath = `${silksongPath}/BepInEx` bepinexFolderPath = `${silksongPath}/BepInEx`;
bepinexBackupPath = `${silksongPath}/BepInEx-Backup` bepinexBackupPath = `${silksongPath}/BepInEx-Backup`;
store.set('silksong-path', silksongPath); store.set("silksong-path", silksongPath);
} }
ipcMain.handle('load-path', () => { ipcMain.handle("load-path", () => {
silksongPath = store.get('silksong-path'); silksongPath = store.get("silksong-path");
if (silksongPath == undefined) { if (silksongPath == undefined) {
return ""; return "";
} }
return silksongPath; return silksongPath;
}); });
function saveBepinexVersion(version) { function saveBepinexVersion(version) {
bepinexVersion = version; bepinexVersion = version;
if (bepinexVersion == undefined) { if (bepinexVersion == undefined) {
bepinexStore.delete('bepinex-version'); bepinexStore.delete("bepinex-version");
return; return;
} }
bepinexStore.set('bepinex-version', version); bepinexStore.set("bepinex-version", version);
}; }
ipcMain.handle('load-bepinex-version', () => { ipcMain.handle("load-bepinex-version", () => {
bepinexVersion = bepinexStore.get('bepinex-version'); bepinexVersion = bepinexStore.get("bepinex-version");
return bepinexVersion; return bepinexVersion;
}); });
function saveBepinexBackupVersion(version) { function saveBepinexBackupVersion(version) {
bepinexBackupVersion = version; bepinexBackupVersion = version;
if (bepinexBackupVersion == undefined) { if (bepinexBackupVersion == undefined) {
bepinexStore.delete('bepinex-backup-version'); bepinexStore.delete("bepinex-backup-version");
return; return;
} }
bepinexStore.set('bepinex-backup-version', version); bepinexStore.set("bepinex-backup-version", version);
}; }
ipcMain.handle('load-bepinex-backup-version', () => { ipcMain.handle("load-bepinex-backup-version", () => {
bepinexBackupVersion = bepinexStore.get('bepinex-backup-version'); bepinexBackupVersion = bepinexStore.get("bepinex-backup-version");
return bepinexBackupVersion; return bepinexBackupVersion;
}); });
ipcMain.handle('save-nexus-api', (event, api) => { ipcMain.handle("save-nexus-api", (event, api) => {
nexusAPI = api; nexusAPI = api;
createNexus() createNexus();
store.set('nexus-api', nexusAPI); store.set("nexus-api", nexusAPI);
}); });
ipcMain.handle('load-nexus-api', () => { ipcMain.handle("load-nexus-api", () => {
nexusAPI = store.get('nexus-api'); nexusAPI = store.get("nexus-api");
if (nexusAPI == undefined) { if (nexusAPI == undefined) {
return ""; return "";
} }
return nexusAPI; return nexusAPI;
}); });
ipcMain.handle("save-theme", (event, theme, lacePinState) => {
ipcMain.handle('save-theme', (event, theme, lacePinState) => { store.set("theme.theme", theme);
store.set('theme.theme', theme); store.set("theme.lacePinState", lacePinState);
store.set('theme.lacePinState', lacePinState);
}); });
ipcMain.handle('load-theme', () => { ipcMain.handle("load-theme", () => {
theme = [store.get('theme.theme'), store.get('theme.lacePinState')]; const theme = [store.get("theme.theme"), store.get("theme.lacePinState")];
if (theme[0] == undefined) { if (theme[0] == undefined) {
return ["Silksong", false]; return ["Silksong", false];
} }
return theme; return theme;
@@ -156,45 +153,43 @@ async function fileExists(filePath) {
} }
} }
ipcMain.handle('delete-data', async () => { ipcMain.handle("delete-data", async () => {
if (await fileExists(dataPath)) { if (await fileExists(dataPath)) {
await fs.unlink(dataPath) await fs.unlink(dataPath);
} }
}); });
ipcMain.handle('export-data', async () => { ipcMain.handle("export-data", async () => {
if (!await fileExists(dataPath)) { if (!(await fileExists(dataPath))) {
return return;
} }
const { canceled, filePath } = await dialog.showSaveDialog({ const { canceled, filePath } = await dialog.showSaveDialog({
title: 'Export Data', title: "Export Data",
defaultPath: 'config.json', defaultPath: "config.json",
filters: [ filters: [{ name: "JSON", extensions: ["json"] }],
{ name: 'JSON', extensions: ['json'] } });
]
})
if (canceled || !filePath) return if (canceled || !filePath) return;
await fs.copyFile(dataPath, filePath) await fs.copyFile(dataPath, filePath);
}) });
ipcMain.handle('import-data', async () => { ipcMain.handle("import-data", async () => {
const { canceled, filePaths } = await dialog.showOpenDialog({ const { canceled, filePaths } = await dialog.showOpenDialog({
title: 'Import Data', title: "Import Data",
properties: ['openFile'], properties: ["openFile"],
filters: [{ name: 'JSON', extensions: ['json'] }] filters: [{ name: "JSON", extensions: ["json"] }],
}) });
if (canceled || !filePaths) return false if (canceled || !filePaths) return false;
if(await fileExists(dataPath)) { if (await fileExists(dataPath)) {
await fs.unlink(dataPath) await fs.unlink(dataPath);
} }
await fs.copyFile(filePaths[0], dataPath,fs.constants.COPYFILE_EXCL) await fs.copyFile(filePaths[0], dataPath, fs.constants.COPYFILE_EXCL);
return true return true;
}) });
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
////////////////////// BEPINEX /////////////////////// ////////////////////// BEPINEX ///////////////////////
@@ -202,157 +197,151 @@ ipcMain.handle('import-data', async () => {
async function installBepinex() { async function installBepinex() {
if (await fileExists(bepinexBackupPath)) { if (await fileExists(bepinexBackupPath)) {
if (await fileExists(`${bepinexBackupPath}/BepInEx`)) { if (await fileExists(`${bepinexBackupPath}/BepInEx`)) {
await fs.cp(`${bepinexBackupPath}/BepInEx`, bepinexFolderPath, { recursive: true }) await fs.cp(`${bepinexBackupPath}/BepInEx`, bepinexFolderPath, {
recursive: true,
});
} }
for (const file of bepinexFiles) { for (const file of bepinexFiles) {
const filePath = `${silksongPath}/${file}` const filePath = `${silksongPath}/${file}`;
if (await fileExists(`${bepinexBackupPath}/${file}`)) { if (await fileExists(`${bepinexBackupPath}/${file}`)) {
await fs.copyFile(`${bepinexBackupPath}/${file}`, filePath) await fs.copyFile(`${bepinexBackupPath}/${file}`, filePath);
} }
} }
await fs.rm(bepinexBackupPath, { recursive: true }) await fs.rm(bepinexBackupPath, { recursive: true });
bepinexBackupVersion = bepinexStore.get('bepinex-backup-version') bepinexBackupVersion = bepinexStore.get("bepinex-backup-version");
saveBepinexVersion(bepinexBackupVersion) saveBepinexVersion(bepinexBackupVersion);
saveBepinexBackupVersion(undefined) saveBepinexBackupVersion(undefined);
} } else {
else { const GITHUB_URL = "https://api.github.com/repos/bepinex/bepinex/releases/latest";
const GITHUB_URL = "https://api.github.com/repos/bepinex/bepinex/releases/latest"
const res = await fetch(GITHUB_URL, { const res = await fetch(GITHUB_URL, {
headers: { headers: {
"User-Agent": "SilkFlyLauncher/1.0.0", "User-Agent": "SilkFlyLauncher/1.0.0",
"Accept": "application/vnd.github+json", Accept: "application/vnd.github+json",
} },
}) });
if (!res.ok) { if (!res.ok) {
throw new Error(`GitHub API error: ${res.status}`) throw new Error(`GitHub API error: ${res.status}`);
} }
const release = await res.json(); const release = await res.json();
const asset = release.assets.find( const asset = release.assets.find((a) => a.name.endsWith(".zip") && a.name.toLowerCase().includes("win_x64"));
a => a.name.endsWith(".zip") && a.name.toLowerCase().includes("win_x64")
);
const download = await fetch(asset.browser_download_url) const download = await fetch(asset.browser_download_url);
if (!download.ok) { if (!download.ok) {
throw new Error("Download error"); throw new Error("Download error");
} }
const filePath = `${userSavePath}\\bepinex.zip` const filePath = `${userSavePath}\\bepinex.zip`;
await pipeline( await pipeline(download.body, createWriteStream(filePath));
download.body,
createWriteStream(filePath)
)
await extract(filePath, { dir: silksongPath}) await extract(filePath, { dir: silksongPath });
await fs.unlink(filePath) await fs.unlink(filePath);
saveBepinexVersion(release.tag_name) saveBepinexVersion(release.tag_name);
} }
} }
ipcMain.handle('install-bepinex', async () => { ipcMain.handle("install-bepinex", async () => {
await installBepinex() await installBepinex();
}) });
async function uninstallBepinex() { async function uninstallBepinex() {
if (await fileExists(bepinexFolderPath)) { if (await fileExists(bepinexFolderPath)) {
await fs.rm(bepinexFolderPath, { recursive: true }) await fs.rm(bepinexFolderPath, { recursive: true });
} }
for (const file of bepinexFiles) { for (const file of bepinexFiles) {
const filePath = `${silksongPath}/${file}` const filePath = `${silksongPath}/${file}`;
if (await fileExists(filePath)) { if (await fileExists(filePath)) {
await fs.unlink(filePath) await fs.unlink(filePath);
} }
} }
saveBepinexVersion(undefined) saveBepinexVersion(undefined);
} }
ipcMain.handle('uninstall-bepinex', async () => { ipcMain.handle("uninstall-bepinex", async () => {
await uninstallBepinex() await uninstallBepinex();
}) });
async function backupBepinex() { async function backupBepinex() {
if (await fileExists(bepinexBackupPath) == false) { if ((await fileExists(bepinexBackupPath)) == false) {
await fs.mkdir(bepinexBackupPath) await fs.mkdir(bepinexBackupPath);
} }
if (await fileExists(bepinexFolderPath)) { if (await fileExists(bepinexFolderPath)) {
await fs.cp(bepinexFolderPath, `${bepinexBackupPath}/BepInEx`, { recursive: true }) await fs.cp(bepinexFolderPath, `${bepinexBackupPath}/BepInEx`, {
recursive: true,
});
} }
for (const file of bepinexFiles) { for (const file of bepinexFiles) {
const filePath = `${silksongPath}/${file}` const filePath = `${silksongPath}/${file}`;
if (await fileExists(filePath)) { if (await fileExists(filePath)) {
await fs.copyFile(filePath, `${bepinexBackupPath}/${file}`) await fs.copyFile(filePath, `${bepinexBackupPath}/${file}`);
} }
} }
saveBepinexBackupVersion(bepinexVersion) saveBepinexBackupVersion(bepinexVersion);
await uninstallBepinex() await uninstallBepinex();
} }
ipcMain.handle('backup-bepinex', async () => { ipcMain.handle("backup-bepinex", async () => {
await backupBepinex() await backupBepinex();
}) });
ipcMain.handle('delete-bepinex-backup', async () => { ipcMain.handle("delete-bepinex-backup", async () => {
if (await fileExists(bepinexBackupPath)) { if (await fileExists(bepinexBackupPath)) {
await fs.rm(bepinexBackupPath, { recursive: true }) await fs.rm(bepinexBackupPath, { recursive: true });
saveBepinexBackupVersion(undefined) saveBepinexBackupVersion(undefined);
} }
}) });
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
/////////////////////// NEXUS //////////////////////// /////////////////////// NEXUS ////////////////////////
async function createNexus() { async function createNexus() {
if (nexusAPI == undefined) { if (nexusAPI == undefined) {
return return;
} }
try { try {
nexus = await Nexus.create( nexus = await Nexus.create(nexusAPI, "silk-fly-launcher", "1.0.0", "hollowknightsilksong");
nexusAPI,
'silk-fly-launcher',
'1.0.0',
'hollowknightsilksong'
);
} catch (error) { } catch (error) {
nexus = undefined console.log(error);
nexus = undefined;
} }
} }
ipcMain.handle('verify-nexus-api', async () => { ipcMain.handle("verify-nexus-api", async () => {
return await verifyNexusAPI() return await verifyNexusAPI();
}) });
async function verifyNexusAPI() { async function verifyNexusAPI() {
if (nexus == undefined) { if (nexus == undefined) {
return false return false;
} }
if (await nexus.getValidationResult()) { if (await nexus.getValidationResult()) {
return true return true;
} }
} }
ipcMain.handle('get-latest-mods', async () => { ipcMain.handle("get-latest-mods", async () => {
if (nexus == undefined) { if (nexus == undefined) {
return return;
} }
mods = await nexus.getLatestAdded() const mods = await nexus.getLatestAdded();
return mods return mods;
}) });
ipcMain.handle('download-mod', async (event, link) => { ipcMain.handle("download-mod", async (event, link) => {
if (nexus == undefined) { if (nexus == undefined) {
return return;
} }
const nexusWindow = new BrowserWindow({ const nexusWindow = new BrowserWindow({
@@ -362,46 +351,43 @@ ipcMain.handle('download-mod', async (event, link) => {
parent: mainWindow, parent: mainWindow,
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true contextIsolation: true,
} },
}) });
nexusWindow.loadURL(link) nexusWindow.loadURL(link);
}) });
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
//////////////////// UNCATEGORIZE //////////////////// //////////////////// UNCATEGORIZE ////////////////////
ipcMain.handle('auto-detect-game-path', async () => { ipcMain.handle("auto-detect-game-path", async () => {
const defaultsSilksongPaths = [ const defaultsSilksongPaths = [":/Program Files (x86)/Steam/steamapps/common/Hollow Knight Silksong", ":/SteamLibrary/steamapps/common/Hollow Knight Silksong"];
":/Program Files (x86)/Steam/steamapps/common/Hollow Knight Silksong",
":/SteamLibrary/steamapps/common/Hollow Knight Silksong"
]
for (const path of defaultsSilksongPaths) { for (const path of defaultsSilksongPaths) {
for (let i = 'A'.charCodeAt(0); i <= 'Z'.charCodeAt(0); i++) { for (let i = "A".charCodeAt(0); i <= "Z".charCodeAt(0); i++) {
const fullPath = `${String.fromCharCode(i)}${path}` const fullPath = `${String.fromCharCode(i)}${path}`;
if (await fileExists(fullPath)) { if (await fileExists(fullPath)) {
saveSilksongPath(fullPath) saveSilksongPath(fullPath);
return return;
} }
} }
} }
}) });
ipcMain.handle('load-main-page', () => { ipcMain.handle("load-main-page", () => {
htmlFile = "index.html" htmlFile = "index.html";
mainWindow.loadFile(`renderer/${htmlFile}`) mainWindow.loadFile(`renderer/${htmlFile}`);
}) });
ipcMain.handle('get-page', () => { ipcMain.handle("get-page", () => {
return htmlFile return htmlFile;
}) });
ipcMain.handle('open-link', async (event, link) => { ipcMain.handle("open-link", async (event, link) => {
await shell.openExternal(link) await shell.openExternal(link);
}) });
ipcMain.handle('open-window', async (event, file) => { ipcMain.handle("open-window", async (event, file) => {
const win = new BrowserWindow({ const win = new BrowserWindow({
width: 600, width: 600,
height: 720, height: 720,
@@ -409,32 +395,30 @@ ipcMain.handle('open-window', async (event, file) => {
parent: mainWindow, parent: mainWindow,
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true contextIsolation: true,
} },
}) });
win.title = file win.title = file;
win.loadFile(file) win.loadFile(file);
}) });
ipcMain.handle('launch-game', async (event, mode) => { ipcMain.handle("launch-game", async (event, mode) => {
const silksongExecutablePath = `${silksongPath}/Hollow Knight Silksong.exe` const silksongExecutablePath = `${silksongPath}/Hollow Knight Silksong.exe`;
if (mode === "modded"){ if (mode === "modded") {
if (await fileExists(bepinexFolderPath)) { if (await fileExists(bepinexFolderPath)) {
await shell.openExternal(silksongExecutablePath) await shell.openExternal(silksongExecutablePath);
} } else {
else { await installBepinex();
await installBepinex() await shell.openExternal(silksongExecutablePath);
await shell.openExternal(silksongExecutablePath)
} }
} }
if (mode === "vanilla"){ if (mode === "vanilla") {
if (await fileExists(bepinexFolderPath)) { if (await fileExists(bepinexFolderPath)) {
await backupBepinex() await backupBepinex();
await shell.openExternal(silksongExecutablePath) await shell.openExternal(silksongExecutablePath);
} } else {
else { await shell.openExternal(silksongExecutablePath);
await shell.openExternal(silksongExecutablePath)
} }
} }
}) });

View File

@@ -1,20 +1,21 @@
{ {
"name": "silkflylauncher", "name": "silkflylauncher",
"version": "1.0.0", "version": "1.0.0",
"description": "A launcher and manager for silksong bepinex mods written in javascript", "description": "A launcher and manager for silksong bepinex mods written in javascript",
"main": "main.js", "main": "main.js",
"scripts": { "type": "module",
"start": "electron .", "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "start": "electron .",
}, "test": "echo \"Error: no test specified\" && exit 1"
"author": "GabiZar", },
"license": "SEE LICENSE IN LICENSE", "author": "GabiZar",
"devDependencies": { "license": "GPL-3.0",
"electron": "^39.2.7" "devDependencies": {
}, "electron": "^39.2.7"
"dependencies": { },
"@nexusmods/nexus-api": "^1.1.5", "dependencies": {
"electron-store": "^11.0.2", "@nexusmods/nexus-api": "^1.1.5",
"extract-zip": "^2.0.1" "electron-store": "^11.0.2",
} "extract-zip": "^2.0.1"
}
} }

View File

@@ -1,47 +1,47 @@
const { contextBridge, ipcRenderer } = require('electron') const { contextBridge, ipcRenderer } = require("electron");
const VERSION = "1.0.0" const VERSION = "1.0.0";
contextBridge.exposeInMainWorld('versions', { contextBridge.exposeInMainWorld("versions", {
silkFlyLauncher: () => VERSION, silkFlyLauncher: () => VERSION,
node: () => process.versions.node, node: () => process.versions.node,
chromium: () => process.versions.chrome, chromium: () => process.versions.chrome,
electron: () => process.versions.electron electron: () => process.versions.electron,
}); });
contextBridge.exposeInMainWorld('files', { contextBridge.exposeInMainWorld("files", {
delete: () => ipcRenderer.invoke('delete-data'), delete: () => ipcRenderer.invoke("delete-data"),
export: () => ipcRenderer.invoke('export-data'), export: () => ipcRenderer.invoke("export-data"),
import: () => ipcRenderer.invoke('import-data'), import: () => ipcRenderer.invoke("import-data"),
autoDetectGamePath: () => ipcRenderer.invoke('auto-detect-game-path'), autoDetectGamePath: () => ipcRenderer.invoke("auto-detect-game-path"),
saveSilksongPath: (path) => ipcRenderer.invoke('save-path', path), saveSilksongPath: (path) => ipcRenderer.invoke("save-path", path),
loadSilksongPath: () => ipcRenderer.invoke('load-path'), loadSilksongPath: () => ipcRenderer.invoke("load-path"),
loadBepinexVersion: () => ipcRenderer.invoke('load-bepinex-version'), loadBepinexVersion: () => ipcRenderer.invoke("load-bepinex-version"),
loadBepinexBackupVersion: () => ipcRenderer.invoke('load-bepinex-backup-version'), loadBepinexBackupVersion: () => ipcRenderer.invoke("load-bepinex-backup-version"),
saveNexusAPI: (api) => ipcRenderer.invoke('save-nexus-api', api), saveNexusAPI: (api) => ipcRenderer.invoke("save-nexus-api", api),
loadNexusAPI: () => ipcRenderer.invoke('load-nexus-api'), loadNexusAPI: () => ipcRenderer.invoke("load-nexus-api"),
saveTheme: (theme, lacePinState) => ipcRenderer.invoke('save-theme', theme, lacePinState), saveTheme: (theme, lacePinState) => ipcRenderer.invoke("save-theme", theme, lacePinState),
loadTheme: () => ipcRenderer.invoke('load-theme') loadTheme: () => ipcRenderer.invoke("load-theme"),
}); });
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld("electronAPI", {
openExternalLink: (url) => ipcRenderer.invoke('open-link', url), openExternalLink: (url) => ipcRenderer.invoke("open-link", url),
openWindow: (file) => ipcRenderer.invoke('open-window', file), openWindow: (file) => ipcRenderer.invoke("open-window", file),
launchGame: (mode) => ipcRenderer.invoke('launch-game', mode), launchGame: (mode) => ipcRenderer.invoke("launch-game", mode),
loadMainPage: () => ipcRenderer.invoke('load-main-page'), loadMainPage: () => ipcRenderer.invoke("load-main-page"),
getPage: () => ipcRenderer.invoke('get-page') getPage: () => ipcRenderer.invoke("get-page"),
}); });
contextBridge.exposeInMainWorld('bepinex', { contextBridge.exposeInMainWorld("bepinex", {
install: () => ipcRenderer.invoke('install-bepinex'), install: () => ipcRenderer.invoke("install-bepinex"),
uninstall: () => ipcRenderer.invoke('uninstall-bepinex'), uninstall: () => ipcRenderer.invoke("uninstall-bepinex"),
backup: () => ipcRenderer.invoke('backup-bepinex'), backup: () => ipcRenderer.invoke("backup-bepinex"),
deleteBackup: () => ipcRenderer.invoke('delete-bepinex-backup') deleteBackup: () => ipcRenderer.invoke("delete-bepinex-backup"),
}) });
contextBridge.exposeInMainWorld('nexus', { contextBridge.exposeInMainWorld("nexus", {
verifyAPI: () => ipcRenderer.invoke('verify-nexus-api'), verifyAPI: () => ipcRenderer.invoke("verify-nexus-api"),
getLatestMods: () => ipcRenderer.invoke('get-latest-mods'), getLatestMods: () => ipcRenderer.invoke("get-latest-mods"),
download: (link) => ipcRenderer.invoke('download-mod', link) download: (link) => ipcRenderer.invoke("download-mod", link),
}) });

View File

@@ -0,0 +1 @@
<svg class="social-github" role="img" focusable="false" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14"><path d="m 7,1.1480539 c -3.313515,0 -6,2.68652 -6,6 0,2.65087 1.719175,4.9003701 4.103505,5.6933601 C 5.403301,12.897114 5.5,12.711544 5.5,12.552834 l 0,-1.11716 C 3.831039,11.798964 3.483359,10.728164 3.483359,10.728164 3.210913,10.034324 2.817388,9.8497439 2.817388,9.8497439 c -0.544456,-0.37205 0.04101,-0.36474 0.04101,-0.36474 0.602563,0.0425 0.919922,0.6186401 0.919922,0.6186401 0.535136,0.91698 1.403301,0.65187 1.746088,0.49855 0.05371,-0.38773 0.208951,-0.6523401 0.380854,-0.8022501 -1.333048,-0.15137 -2.733437,-0.66603 -2.733437,-2.96534 0,-0.65528 0.234379,-1.19045 0.618204,-1.61036 -0.0625,-0.15137 -0.267612,-0.76171 0.05764,-1.5879 0,0 0.503913,-0.16113 1.650349,0.61523 0.478981,-0.13282 0.992214,-0.19969 1.501981,-0.20214 0.509767,0.002 1.023903,0.0693 1.502913,0.20214 1.145534,-0.77636 1.648427,-0.61523 1.648427,-0.61523 0.326709,0.82616 0.121107,1.4365 0.0591,1.5879 0.384786,0.41991 0.61768,0.95508 0.61768,1.61036 0,2.30467 -1.403797,2.81199 -2.739729,2.96044 0.214806,0.18606 0.411641,0.5517401 0.411641,1.1113201 0,0.80274 0,1.44923 0,1.64647 0,0.15967 0.09615,0.34669 0.400369,0.28812 C 11.283214,12.046534 13,9.7984539 13,7.1480839 c 0,-3.31348 -2.686544,-6.00003 -6,-6.00003 z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,195 +1,197 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>Silk Fly Launcher</title> <title>Silk Fly Launcher</title>
<link rel="stylesheet" href="style.css" /> <link rel="stylesheet" href="style.css" />
</head> </head>
<body> <body>
<div class="app">
<video autoplay muted loop class="background-video" id="background-video" src="assets/background/Silksong.mp4" type="video/mp4"></video>
<div class="app"> <!-- Sidebar -->
<aside class="sidebar">
<video autoplay muted loop class="background-video" id="background-video" src="assets/background/Silksong.mp4" type="video/mp4"></video> <div class="logo" onclick="navigate('home')">
<img src="assets/logo.png" alt="Silk Fly Launcher Logo" class="logo-img" />
<!-- Sidebar --> <h5 class="logo-title">Silk Fly Launcher</h5>
<aside class="sidebar">
<div class="logo" onclick="navigate('home')">
<img src="assets/logo.png" alt="Silk Fly Launcher Logo" class="logo-img"/>
<h5 class="logo-title">Silk Fly Launcher</h1>
</div>
<nav class="nav">
<div class="nav-section">
<span class="nav-title">Execute Silksong</span>
<button onclick="launch('vanilla')">
<img src="vanilla_launch_icon.png" class="button-icon"/>
Run Vanilla
<button onclick="launch('modded')">
<img src="modded_launch_icon.png" class="button-icon"/>
Run Modded
</div> </div>
<div class="nav-section"> <nav class="nav">
<span class="nav-title">Mods</span> <div class="nav-section">
<button onclick="navigate('mods-installed')"> <span class="nav-title">Execute Silksong</span>
<img src="installed_mods_icon.png" class="button-icon"/> <button onclick="launch('vanilla')">
Installed <img src="vanilla_launch_icon.png" class="button-icon" />
<button onclick="navigate('mods-online')"> Run Vanilla
<img src="online_mods_icon.png" class="button-icon"/> </button>
Online <button onclick="launch('modded')">
</div> <img src="modded_launch_icon.png" class="button-icon" />
Run Modded
</button>
</div>
<div class="nav-section"> <div class="nav-section">
<span class="nav-title">Settings</span> <span class="nav-title">Mods</span>
<button onclick="navigate('general-settings')"> <button onclick="navigate('mods-installed')">
<img src="general_settings_icon.png" class="button-icon"/> <img src="installed_mods_icon.png" class="button-icon" />
General Installed
</div> </button>
</nav> <button onclick="navigate('mods-online')">
</aside> <img src="online_mods_icon.png" class="button-icon" />
Online
</button>
</div>
<!-- Main content --> <div class="nav-section">
<main class="content"> <span class="nav-title">Settings</span>
<h1 id="title">Silk Fly Launcher</h1> <button onclick="navigate('general-settings')">
<div class="view" id="view"> <img src="general_settings_icon.png" class="button-icon" />
</div> General
</main> </button>
</div> </div>
</nav>
</aside>
<!-- Template --> <!-- Main content -->
<template id="home-template"> <main class="content">
<h2>About</h2> <h1 id="title">Silk Fly Launcher</h1>
<div class="horizontal-div separated-div"> <div class="view" id="view"></div>
<div class="horizontal-div"> </main>
<img src="assets/logo.png" alt="Silk Fly Launcher Logo" class="big-logo-img"/>
<div>
<h3>Silk Fly Launcher</h3>
<p class="transparent-text">v1.0.0</p>
</div>
</div>
<div class="horizontal-div">
<img onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar/Silk-Fly-Launcher')" src="data:image/svg+xml,%3csvg%20width='98'%20height='96'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20fill-rule='evenodd'%20clip-rule='evenodd'%20d='M48.854%200C21.839%200%200%2022%200%2049.217c0%2021.756%2013.993%2040.172%2033.405%2046.69%202.427.49%203.316-1.059%203.316-2.362%200-1.141-.08-5.052-.08-9.127-13.59%202.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015%204.934.326%207.523%205.052%207.523%205.052%204.367%207.496%2011.404%205.378%2014.235%204.074.404-3.178%201.699-5.378%203.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283%200-5.378%201.94-9.778%205.014-13.2-.485-1.222-2.184-6.275.486-13.038%200%200%204.125-1.304%2013.426%205.052a46.97%2046.97%200%200%201%2012.214-1.63c4.125%200%208.33.571%2012.213%201.63%209.302-6.356%2013.427-5.052%2013.427-5.052%202.67%206.763.97%2011.816.485%2013.038%203.155%203.422%205.015%207.822%205.015%2013.2%200%2018.905-11.404%2023.06-22.324%2024.283%201.78%201.548%203.316%204.481%203.316%209.126%200%206.6-.08%2011.897-.08%2013.526%200%201.304.89%202.853%203.316%202.364%2019.412-6.52%2033.405-24.935%2033.405-46.691C97.707%2022%2075.788%200%2048.854%200z'%20fill='%23fff'/%3e%3c/svg%3e" alt="Github logo" class="logo-img"/>
</div>
</div> </div>
<br>
<ul>
<li>Silk Fly Launcher is a launcher and mod manager for Silksong mods from Nexus, built with Electron.</li>
<li>This product is licensed under the <a href="" class="link" onclick="electronAPI.openWindow('LICENSE')">GNU General Public License Version 3</a>.</li>
<li>This product uses third-party modules or assets under <a href="" class="link" onclick="electronAPI.openWindow('3RD-PARTY-LICENSES')">third-party licenses</a>.</li>
<li>Found a bug or have a feature request? Please <a href="" class="link" onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar/Silk-Fly-Launcher/issues')">create an issue on GitHub</a>.</li>
<li>Made with ♥ by <a href="" class="link" onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar')">GabiZar</a>.</li>
</ul>
<br>
</template>
<template id="installed-mods-template">
<h2>List Of Installed Mods</h2>
<form class="horizontal-div" id="search-form">
<input class="input" id="search-input" type="text" placeholder="Search For Mods...">
<button class="default-button" onclick="searchInstalledMods()">Search</button>
</form>
<div class="mods-container" id="mods-container">
</div>
</template>
<template id="online-mods-template">
<h2>List Of Nexus Mods</h2>
<form class="horizontal-div" id="search-form">
<input class="input" id="search-input" type="text" placeholder="Search For Mods...">
<button class="default-button" onclick="searchNexusMods()">Search</button>
</form>
<div class="mods-container" id="mods-container">
</div>
</template>
<template id="mod-template">
<div class="mod-container">
<div class="mod-text">
<div class="horizontal-div" >
<h3 id="mod-title">Unknown Title</h3>
<p id="mod-author">Unknown author</p>
<p id="mod-endorsements-number">? likes</p>
</div>
<p id="mod-description">No description provided</p>
<p class="transparent-text" id="mod-version">V1.0.0 last update on 01/01/2026</p>
<!-- Template -->
<template id="home-template">
<h2>About</h2>
<div class="horizontal-div separated-div">
<div class="horizontal-div"> <div class="horizontal-div">
<a href="www.nexusmods.com/hollowknightsilksong/mods" class="default-button" id="download-mod-button">Download</a> <img src="assets/logo.png" alt="Silk Fly Launcher Logo" class="big-logo-img" />
<a href="www.nexusmods.com/hollowknightsilksong/mods" class="default-button" id="external-link">Website</a> <div>
<h3>Silk Fly Launcher</h3>
<p class="transparent-text">v1.0.0</p>
</div>
</div>
<div class="horizontal-div">
<img onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar/Silk-Fly-Launcher')" src="assets/github.svg" alt="Github logo" class="logo-img invert-color" />
</div> </div>
</div> </div>
<br />
<img class="mod-icon" src="assets/placeholder_icon.png" alt="mod icon" id="mod-icon"> <ul>
</div> <li>Silk Fly Launcher is a launcher and mod manager for Silksong mods from Nexus, built with Electron.</li>
</template> <li>This product is licensed under the <a href="" class="link" onclick="electronAPI.openWindow('LICENSE')">GNU General Public License Version 3</a>.</li>
<li>This product uses third-party modules or assets under <a href="" class="link" onclick="electronAPI.openWindow('3RD-PARTY-LICENSES')">third-party licenses</a>.</li>
<template id="settings-template"> <li>
<h2>General settings</h2> Found a bug or have a feature request? Please
<div class="horizontal-div"> <a href="" class="link" onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar/Silk-Fly-Launcher/issues')">create an issue on GitHub</a>.
<label>Enter Silksong path: </label> </li>
<input type="text" class="input" id="silksong-path-input" name="silksong-path-input"> <li>Made with ♥ by <a href="" class="link" onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar')">GabiZar</a>.</li>
<button class="default-button" onclick="autoDetectGamePath()">Auto Detect</button>
</div>
<div class="horizontal-div">
<label>Themes: </label>
<div class="themes-div">
<div class="default-button longer-button" id="themes-button" onclick="toggleThemesMenu()">Silksong</div>
<div class="themes-menu longer-button" id="themes-menu">
<li onclick="changeTheme('Silksong')">Silksong</li>
<li onclick="changeTheme('Citadel of song')">Citadel of song</li>
<li onclick="changeTheme('Cradle')">Cradle</li>
<li onclick="changeTheme('Abyss')">Abyss</li>
<li onclick="changeTheme('Greyroot')">Greyroot</li>
<li onclick="changeTheme('Surface')">Surface</li>
<li onclick="changeTheme('Steel')">Steel</li>
</div>
</div>
<label class="lace-pin-checkbox-container">
<input type="checkbox" name="lace-pin" id="lace-pin">
<span class="checkmark"></span>
Lace Pin
</label>
</div>
<br>
<h2>BepInEx</h2>
<p class="transparent-text" id="bepinex-version-text"></p>
<div class="horizontal-div">
<button class="default-button" onclick="installBepinex()">Install</button>
<button class="important-button" onclick="uninstallBepinex()">Uninstall</button>
<button class="default-button" onclick="backupBepinex()">Backup</button>
<button class="important-button" onclick="deleteBepinexBackup()">Delete Backup</button>
</div>
<br>
<h2>Nexus</h2>
<p class="transparent-text" id="bepinex-version-text"></p>
<div class="horizontal-div">
<label for="nexus-api-label">Enter your nexus api: </label>
<input type="text" class="input" id="nexus-api-input" name="nexus-api-input">
<img class="nexus-check-image" id="nexus-check-image" src="assets/cross.svg">
<button class="default-button" onclick="verifyNexusAPI()">Verify</button>
</div>
<br>
<h2>Import/Export</h2>
<div class="horizontal-div">
<button class="default-button" onclick="importData()">Import Data</button>
<button class="default-button" onclick="exportData()">Export Data</button>
<button class="important-button" onclick="deleteData()">Delete All Data</button>
</div>
<br>
<h2>Debugging</h2>
<div class="horizontal-div">
<h3>Versions: </h3>
<ul id="versions-list">
<li id="Silk-Fly-Launcher"></li>
<li id="Electron"></li>
<li id="Node"></li>
<li id="Chromium"></li>
</ul> </ul>
<p id="version-text"></p> <br />
</div> </template>
</template>
<script src="renderer.js"></script> <template id="installed-mods-template">
<h2>List Of Installed Mods</h2>
<form class="horizontal-div" id="search-form">
<input class="input" id="search-input" type="text" placeholder="Search For Mods..." />
<button class="default-button" onclick="searchInstalledMods()">Search</button>
</form>
<div class="mods-container" id="mods-container"></div>
</template>
</body> <template id="online-mods-template">
<h2>List Of Nexus Mods</h2>
<form class="horizontal-div" id="search-form">
<input class="input" id="search-input" type="text" placeholder="Search For Mods..." />
<button class="default-button" onclick="searchNexusMods()">Search</button>
</form>
<div class="mods-container" id="mods-container"></div>
</template>
<template id="mod-template">
<div class="mod-container">
<div class="mod-text">
<div class="horizontal-div">
<h3 id="mod-title">Unknown Title</h3>
<p id="mod-author">Unknown author</p>
<p id="mod-endorsements-number">? likes</p>
</div>
<p id="mod-description">No description provided</p>
<p class="transparent-text" id="mod-version">V1.0.0 last update on 01/01/2026</p>
<div class="horizontal-div">
<a href="www.nexusmods.com/hollowknightsilksong/mods" class="default-button" id="download-mod-button">Download</a>
<a href="www.nexusmods.com/hollowknightsilksong/mods" class="default-button" id="external-link">Website</a>
</div>
</div>
<img class="mod-icon" src="assets/placeholder_icon.png" alt="mod icon" id="mod-icon" />
</div>
</template>
<template id="settings-template">
<h2>General settings</h2>
<div class="horizontal-div">
<label>Enter Silksong path: </label>
<input type="text" class="input" id="silksong-path-input" name="silksong-path-input" />
<button class="default-button" onclick="autoDetectGamePath()">Auto Detect</button>
</div>
<div class="horizontal-div">
<label>Themes: </label>
<div class="themes-div">
<div class="default-button longer-button" id="themes-button" onclick="toggleThemesMenu()">Silksong</div>
<div class="themes-menu longer-button" id="themes-menu">
<li onclick="changeTheme('Silksong')">Silksong</li>
<li onclick="changeTheme('Citadel of song')">Citadel of song</li>
<li onclick="changeTheme('Cradle')">Cradle</li>
<li onclick="changeTheme('Abyss')">Abyss</li>
<li onclick="changeTheme('Greyroot')">Greyroot</li>
<li onclick="changeTheme('Surface')">Surface</li>
<li onclick="changeTheme('Steel')">Steel</li>
</div>
</div>
<label class="lace-pin-checkbox-container">
<input type="checkbox" name="lace-pin" id="lace-pin" />
<span class="checkmark"></span>
Lace Pin
</label>
</div>
<br />
<h2>BepInEx</h2>
<p class="transparent-text" id="bepinex-version-text"></p>
<div class="horizontal-div">
<button class="default-button" onclick="installBepinex()">Install</button>
<button class="important-button" onclick="uninstallBepinex()">Uninstall</button>
<button class="default-button" onclick="backupBepinex()">Backup</button>
<button class="important-button" onclick="deleteBepinexBackup()">Delete Backup</button>
</div>
<br />
<h2>Nexus</h2>
<p class="transparent-text" id="bepinex-version-text"></p>
<div class="horizontal-div">
<label for="nexus-api-label">Enter your nexus api: </label>
<input type="text" class="input" id="nexus-api-input" name="nexus-api-input" />
<img class="nexus-check-image" id="nexus-check-image" src="assets/cross.svg" />
<button class="default-button" onclick="verifyNexusAPI()">Verify</button>
</div>
<br />
<h2>Import/Export</h2>
<div class="horizontal-div">
<button class="default-button" onclick="importData()">Import Data</button>
<button class="default-button" onclick="exportData()">Export Data</button>
<button class="important-button" onclick="deleteData()">Delete All Data</button>
</div>
<br />
<h2>Debugging</h2>
<div class="horizontal-div">
<h3>Versions:</h3>
<ul id="versions-list">
<li id="Silk-Fly-Launcher"></li>
<li id="Electron"></li>
<li id="Node"></li>
<li id="Chromium"></li>
</ul>
<p id="version-text"></p>
</div>
</template>
<script src="renderer.js"></script>
</body>
</html> </html>

View File

@@ -8,13 +8,13 @@ const onlineModsTemplate = document.getElementById("online-mods-template");
const settingsTemplate = document.getElementById("settings-template"); const settingsTemplate = document.getElementById("settings-template");
const modTemplate = document.getElementById("mod-template"); const modTemplate = document.getElementById("mod-template");
let oldPage let oldPage;
let actualTheme = [] let actualTheme = [];
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
///////////////// CONST FOR WELCOME ////////////////// ///////////////// CONST FOR WELCOME //////////////////
let actualPage = 0 let actualPage = 0;
const pageDiv = document.getElementById("page"); const pageDiv = document.getElementById("page");
const buttonDiv = document.getElementById("button-div"); const buttonDiv = document.getElementById("button-div");
@@ -31,16 +31,15 @@ const tutorialTemplate = document.getElementById("tutorial-template");
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
////////////////////// STARTUP /////////////////////// ////////////////////// STARTUP ///////////////////////
on_startup() on_startup();
async function on_startup() { async function on_startup() {
if (await electronAPI.getPage() == "index.html") { if ((await electronAPI.getPage()) == "index.html") {
const theme = await files.loadTheme() const theme = await files.loadTheme();
changeTheme(theme[0], theme[1]) changeTheme(theme[0], theme[1]);
navigate("home") navigate("home");
} } else if ((await electronAPI.getPage()) == "welcome.html") {
else if (await electronAPI.getPage() == "welcome.html") { welcomeNavigate();
welcomeNavigate()
} }
} }
@@ -49,223 +48,220 @@ async function on_startup() {
async function navigate(page) { async function navigate(page) {
if (oldPage == page) { if (oldPage == page) {
return return;
} }
oldPage = page oldPage = page;
view.replaceChildren() view.replaceChildren();
switch (page) { switch (page) {
case "home": case "home":
title.innerText = "Silk Fly Launcher"; title.innerText = "Silk Fly Launcher";
const HomeTemplateCopy = HomeTemplate.content.cloneNode(true) const HomeTemplateCopy = HomeTemplate.content.cloneNode(true);
view.appendChild(HomeTemplateCopy) view.appendChild(HomeTemplateCopy);
break; break;
case "mods-installed": case "mods-installed":
title.innerText = "Installed Mods"; title.innerText = "Installed Mods";
const installedModsTemplateCopy = installedModsTemplate.content.cloneNode(true) const installedModsTemplateCopy = installedModsTemplate.content.cloneNode(true);
const searchFormInstalled = installedModsTemplateCopy.getElementById("search-form") const searchFormInstalled = installedModsTemplateCopy.getElementById("search-form");
searchFormInstalled.addEventListener('submit', async function(event) { searchFormInstalled.addEventListener("submit", async function (event) {
event.preventDefault() event.preventDefault();
}) });
view.appendChild(installedModsTemplateCopy) view.appendChild(installedModsTemplateCopy);
break; break;
case "mods-online": case "mods-online":
title.innerText = "Online Mods"; title.innerText = "Online Mods";
const onlineModsTemplateCopy = onlineModsTemplate.content.cloneNode(true) const onlineModsTemplateCopy = onlineModsTemplate.content.cloneNode(true);
const ModsContainer = onlineModsTemplateCopy.getElementById("mods-container") const ModsContainer = onlineModsTemplateCopy.getElementById("mods-container");
const searchFormNexus = onlineModsTemplateCopy.getElementById("search-form") const searchFormNexus = onlineModsTemplateCopy.getElementById("search-form");
searchFormNexus.addEventListener('submit', async function(event) { searchFormNexus.addEventListener("submit", async function (event) {
event.preventDefault() event.preventDefault();
}) });
view.appendChild(onlineModsTemplateCopy) view.appendChild(onlineModsTemplateCopy);
mods = await nexus.getLatestMods() mods = await nexus.getLatestMods();
if (mods == undefined) { if (mods == undefined) {
break; break;
} }
for(const mod of mods) { for (const mod of mods) {
if (mod.name == undefined) { if (mod.name == undefined) {
continue continue;
} }
const modTemplateCopy = modTemplate.content.cloneNode(true) const modTemplateCopy = modTemplate.content.cloneNode(true);
if (mod.name) { if (mod.name) {
const modTitleText = modTemplateCopy.getElementById("mod-title") const modTitleText = modTemplateCopy.getElementById("mod-title");
modTitleText.innerText = mod.name modTitleText.innerText = mod.name;
} }
if (mod.author) { if (mod.author) {
const modAuthorText = modTemplateCopy.getElementById("mod-author") const modAuthorText = modTemplateCopy.getElementById("mod-author");
modAuthorText.innerText = `by ${mod.author}` modAuthorText.innerText = `by ${mod.author}`;
} }
if (mod.endorsement_count) { if (mod.endorsement_count) {
const modEndorsementsNumber = modTemplateCopy.getElementById("mod-endorsements-number") const modEndorsementsNumber = modTemplateCopy.getElementById("mod-endorsements-number");
if (mod.endorsement_count > 1) { if (mod.endorsement_count > 1) {
modEndorsementsNumber.innerText = `${mod.endorsement_count} likes` modEndorsementsNumber.innerText = `${mod.endorsement_count} likes`;
} } else {
else { modEndorsementsNumber.innerText = `${mod.endorsement_count} like`;
modEndorsementsNumber.innerText = `${mod.endorsement_count} like`
} }
} }
if (mod.summary) { if (mod.summary) {
const modDescriptionText = modTemplateCopy.getElementById("mod-description") const modDescriptionText = modTemplateCopy.getElementById("mod-description");
modDescriptionText.innerText = mod.summary modDescriptionText.innerText = mod.summary;
} }
if (mod.picture_url) { if (mod.picture_url) {
const modPicture = modTemplateCopy.getElementById("mod-icon") const modPicture = modTemplateCopy.getElementById("mod-icon");
modPicture.src = mod.picture_url modPicture.src = mod.picture_url;
} }
if (mod.version && mod.updated_timestamp) { if (mod.version && mod.updated_timestamp) {
const modVersionText = modTemplateCopy.getElementById("mod-version") const modVersionText = modTemplateCopy.getElementById("mod-version");
modVersionText.innerText = `V${mod.version} last updated on ${mod.updated_time.slice(0, 10)}` modVersionText.innerText = `V${mod.version} last updated on ${mod.updated_time.slice(0, 10)}`;
} }
const modUrl = `https://www.nexusmods.com/hollowknightsilksong/mods/${mod.mod_id}` const modUrl = `https://www.nexusmods.com/hollowknightsilksong/mods/${mod.mod_id}`;
const modLinkButton = modTemplateCopy.getElementById("external-link") const modLinkButton = modTemplateCopy.getElementById("external-link");
modLinkButton.href = modUrl modLinkButton.href = modUrl;
modLinkButton.addEventListener('click', function(event) { modLinkButton.addEventListener("click", function (event) {
event.preventDefault() event.preventDefault();
const modLink = modLinkButton.href const modLink = modLinkButton.href;
electronAPI.openExternalLink(modLink) electronAPI.openExternalLink(modLink);
}) });
modDownloadButton = modTemplateCopy.getElementById("download-mod-button") modDownloadButton = modTemplateCopy.getElementById("download-mod-button");
modDownloadButton.addEventListener('click', function(event) { modDownloadButton.addEventListener("click", function (event) {
event.preventDefault() event.preventDefault();
const modDownloadLink = `${modUrl}?tab=files` const modDownloadLink = `${modUrl}?tab=files`;
nexus.download(modDownloadLink) nexus.download(modDownloadLink);
}) });
ModsContainer.appendChild(modTemplateCopy) ModsContainer.appendChild(modTemplateCopy);
} }
break; break;
case "general-settings": case "general-settings":
title.innerText = "Settings"; title.innerText = "Settings";
const settingsTemplateCopy = settingsTemplate.content.cloneNode(true) const settingsTemplateCopy = settingsTemplate.content.cloneNode(true);
const silksongPathInput = settingsTemplateCopy.getElementById("silksong-path-input") const silksongPathInput = settingsTemplateCopy.getElementById("silksong-path-input");
const nexusAPIInput = settingsTemplateCopy.getElementById("nexus-api-input") const nexusAPIInput = settingsTemplateCopy.getElementById("nexus-api-input");
const versionsList = settingsTemplateCopy.getElementById("versions-list") const versionsList = settingsTemplateCopy.getElementById("versions-list");
const versionsDictionnary = { const versionsDictionnary = {
"Silk-Fly-Launcher": `Silk Fly Launcher: v${versions.silkFlyLauncher()}`, "Silk-Fly-Launcher": `Silk Fly Launcher: v${versions.silkFlyLauncher()}`,
"Electron": `Electron: v${versions.electron()}`, Electron: `Electron: v${versions.electron()}`,
"Node": `Node.js: v${versions.node()}`, Node: `Node.js: v${versions.node()}`,
"Chromium": `Chromium: v${versions.chromium()}`, Chromium: `Chromium: v${versions.chromium()}`,
} };
const lacePinCheckbox = settingsTemplateCopy.getElementById('lace-pin'); const lacePinCheckbox = settingsTemplateCopy.getElementById("lace-pin");
silksongPathInput.value = await files.loadSilksongPath() silksongPathInput.value = await files.loadSilksongPath();
silksongPathInput.addEventListener('input', async function(event) { silksongPathInput.addEventListener("input", async function (event) {
let silksongPath = silksongPathInput.value let silksongPath = silksongPathInput.value;
files.saveSilksongPath(silksongPath) files.saveSilksongPath(silksongPath);
}); });
nexusAPIInput.value = await files.loadNexusAPI() nexusAPIInput.value = await files.loadNexusAPI();
nexusAPIInput.addEventListener('input', async function(event) { nexusAPIInput.addEventListener("input", async function (event) {
let nexusAPI = nexusAPIInput.value let nexusAPI = nexusAPIInput.value;
files.saveNexusAPI(nexusAPI) files.saveNexusAPI(nexusAPI);
}); });
for(const element of versionsList.children) { for (const element of versionsList.children) {
element.innerText = versionsDictionnary[element.id] element.innerText = versionsDictionnary[element.id];
} }
const theme = await files.loadTheme() const theme = await files.loadTheme();
lacePinCheckbox.checked = theme[1] lacePinCheckbox.checked = theme[1];
lacePinCheckbox.addEventListener('change', async function() { lacePinCheckbox.addEventListener("change", async function () {
if (this.checked) { if (this.checked) {
const theme = await files.loadTheme(); const theme = await files.loadTheme();
changeTheme(theme[0], true); changeTheme(theme[0], true);
toggleThemesMenu() toggleThemesMenu();
} } else {
else {
const theme = await files.loadTheme(); const theme = await files.loadTheme();
changeTheme(theme[0], false); changeTheme(theme[0], false);
toggleThemesMenu() toggleThemesMenu();
} }
}); });
view.appendChild(settingsTemplateCopy) view.appendChild(settingsTemplateCopy);
setBepinexVersion() setBepinexVersion();
setThemeButton() setThemeButton();
verifyNexusAPI() verifyNexusAPI();
break; break;
} }
} }
async function welcomeNavigate() { async function welcomeNavigate() {
pageDiv.replaceChildren() pageDiv.replaceChildren();
switch (actualPage) { switch (actualPage) {
case 0: case 0:
pageDiv.appendChild(welcomeTemplate.content.cloneNode(true)) pageDiv.appendChild(welcomeTemplate.content.cloneNode(true));
buttonDiv.replaceChildren() buttonDiv.replaceChildren();
buttonDiv.appendChild(oneButtonTemplate.content.cloneNode(true)) buttonDiv.appendChild(oneButtonTemplate.content.cloneNode(true));
break; break;
case 1: case 1:
pageDiv.appendChild(silksongPathTemplate.content.cloneNode(true)) pageDiv.appendChild(silksongPathTemplate.content.cloneNode(true));
buttonDiv.replaceChildren() buttonDiv.replaceChildren();
buttonDiv.appendChild(twoButtonTemplate.content.cloneNode(true)) buttonDiv.appendChild(twoButtonTemplate.content.cloneNode(true));
const silksongPathInput = document.getElementById("silksong-path-input") const silksongPathInput = document.getElementById("silksong-path-input");
if (await files.loadSilksongPath() == "") { if ((await files.loadSilksongPath()) == "") {
autoDetectGamePath() autoDetectGamePath();
} } else {
else { document.getElementById("silksong-path-input").value = await files.loadSilksongPath();
document.getElementById("silksong-path-input").value = await files.loadSilksongPath()
} }
silksongPathInput.addEventListener('input', async function(event) { silksongPathInput.addEventListener("input", async function (event) {
let silksongPath = silksongPathInput.value let silksongPath = silksongPathInput.value;
await files.saveSilksongPath(silksongPath) await files.saveSilksongPath(silksongPath);
}); });
break; break;
case 2: case 2:
pageDiv.appendChild(nexusTemplate.content.cloneNode(true)) pageDiv.appendChild(nexusTemplate.content.cloneNode(true));
const nexusLink = document.getElementById("external-link") const nexusLink = document.getElementById("external-link");
nexusLink.addEventListener('click', function(event) { nexusLink.addEventListener("click", function (event) {
event.preventDefault() event.preventDefault();
const url = nexusLink.href const url = nexusLink.href;
electronAPI.openExternalLink(url) electronAPI.openExternalLink(url);
}) });
const nexusAPIInput = document.getElementById("nexus-api-input") const nexusAPIInput = document.getElementById("nexus-api-input");
nexusAPIInput.value = await files.loadNexusAPI() nexusAPIInput.value = await files.loadNexusAPI();
nexusAPIInput.addEventListener('input', async function(event) { nexusAPIInput.addEventListener("input", async function (event) {
let nexusAPI = nexusAPIInput.value let nexusAPI = nexusAPIInput.value;
await files.saveNexusAPI(nexusAPI) await files.saveNexusAPI(nexusAPI);
}); });
break; break;
case 3: case 3:
pageDiv.appendChild(styleTemplate.content.cloneNode(true)) pageDiv.appendChild(styleTemplate.content.cloneNode(true));
break; break;
case 4: case 4:
pageDiv.appendChild(tutorialTemplate.content.cloneNode(true)) pageDiv.appendChild(tutorialTemplate.content.cloneNode(true));
break; break;
case 5: case 5:
electronAPI.loadMainPage() electronAPI.loadMainPage();
break; break;
} }
} }
function next() { function next() {
actualPage++ actualPage++;
welcomeNavigate() welcomeNavigate();
} }
function back() { function back() {
actualPage-- actualPage--;
welcomeNavigate() welcomeNavigate();
} }
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
@@ -273,149 +269,145 @@ function back() {
async function initialImportData() { async function initialImportData() {
if (await files.import()) { if (await files.import()) {
electronAPI.loadMainPage() electronAPI.loadMainPage();
} }
} }
async function importData() { async function importData() {
await files.import() await files.import();
document.getElementById("silksong-path-input").value = await files.loadSilksongPath() document.getElementById("silksong-path-input").value = await files.loadSilksongPath();
document.getElementById("nexus-api-input").value = await files.loadNexusAPI() document.getElementById("nexus-api-input").value = await files.loadNexusAPI();
const lacePinCheckbox = document.getElementById('lace-pin') const lacePinCheckbox = document.getElementById("lace-pin");
const theme = await files.loadTheme() const theme = await files.loadTheme();
lacePinCheckbox.checked = theme[1] lacePinCheckbox.checked = theme[1];
changeTheme(theme[0]) changeTheme(theme[0]);
toggleThemesMenu() toggleThemesMenu();
} }
async function exportData() { async function exportData() {
await files.export() await files.export();
} }
async function deleteData() { async function deleteData() {
const lacePinCheckbox = document.getElementById('lace-pin') const lacePinCheckbox = document.getElementById("lace-pin");
lacePinCheckbox.checked = false lacePinCheckbox.checked = false;
changeTheme("Silksong") changeTheme("Silksong");
toggleThemesMenu() toggleThemesMenu();
await files.delete() await files.delete();
document.getElementById("silksong-path-input").value = await files.loadSilksongPath() document.getElementById("silksong-path-input").value = await files.loadSilksongPath();
document.getElementById("nexus-api-input").value = await files.loadNexusAPI() document.getElementById("nexus-api-input").value = await files.loadNexusAPI();
} }
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
////////////////////// BEPINEX /////////////////////// ////////////////////// BEPINEX ///////////////////////
async function installBepinex() { async function installBepinex() {
await bepinex.install() await bepinex.install();
setBepinexVersion() setBepinexVersion();
} }
async function uninstallBepinex() { async function uninstallBepinex() {
await bepinex.uninstall() await bepinex.uninstall();
setBepinexVersion() setBepinexVersion();
} }
async function backupBepinex() { async function backupBepinex() {
await bepinex.backup() await bepinex.backup();
setBepinexVersion() setBepinexVersion();
} }
async function deleteBepinexBackup() { async function deleteBepinexBackup() {
await bepinex.deleteBackup() await bepinex.deleteBackup();
setBepinexVersion() setBepinexVersion();
} }
async function setBepinexVersion() { async function setBepinexVersion() {
const bepinexVersionText = document.getElementById("bepinex-version-text") const bepinexVersionText = document.getElementById("bepinex-version-text");
if (bepinexVersionText == undefined) { if (bepinexVersionText == undefined) {
return return;
} }
const bepinexVersion = await files.loadBepinexVersion() const bepinexVersion = await files.loadBepinexVersion();
const bepinexBackupVersion = await files.loadBepinexBackupVersion() const bepinexBackupVersion = await files.loadBepinexBackupVersion();
if(bepinexVersion == undefined) { if (bepinexVersion == undefined) {
if(bepinexBackupVersion == undefined) { if (bepinexBackupVersion == undefined) {
bepinexVersionText.innerText = "BepInEx is not installed" bepinexVersionText.innerText = "BepInEx is not installed";
} else {
bepinexVersionText.innerText = `BepInEx ${bepinexBackupVersion} is backed up`;
} }
else { } else {
bepinexVersionText.innerText = `BepInEx ${bepinexBackupVersion} is backed up` bepinexVersionText.innerText = `BepInEx ${bepinexVersion} is installed`;
}
}
else {
bepinexVersionText.innerText = `BepInEx ${bepinexVersion} is installed`
} }
} }
async function searchInstalledMods() { async function searchInstalledMods() {
const searchInput = document.getElementById("search-input") const searchInput = document.getElementById("search-input");
console.log(searchInput.value) console.log(searchInput.value);
} }
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
/////////////////////// NEXUS //////////////////////// /////////////////////// NEXUS ////////////////////////
async function verifyNexusAPI() { async function verifyNexusAPI() {
response = await nexus.verifyAPI() response = await nexus.verifyAPI();
const nexusCheckImage = document.getElementById("nexus-check-image") const nexusCheckImage = document.getElementById("nexus-check-image");
if (nexusCheckImage == undefined) { if (nexusCheckImage == undefined) {
return return;
} }
if (response) { if (response) {
nexusCheckImage.src = "assets/check.svg" nexusCheckImage.src = "assets/check.svg";
} } else {
else { nexusCheckImage.src = "assets/cross.svg";
nexusCheckImage.src = "assets/cross.svg"
} }
} }
async function searchNexusMods() { async function searchNexusMods() {
const searchInput = document.getElementById("search-input") const searchInput = document.getElementById("search-input");
console.log(searchInput.value) console.log(searchInput.value);
} }
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
/////////////////////// THEMES /////////////////////// /////////////////////// THEMES ///////////////////////
function toggleThemesMenu() { function toggleThemesMenu() {
const themesMenu = document.getElementById("themes-menu") const themesMenu = document.getElementById("themes-menu");
if (themesMenu) { if (themesMenu) {
themesMenu.classList.toggle("show") themesMenu.classList.toggle("show");
} }
} }
async function setThemeButton() { async function setThemeButton() {
const themesButton = document.getElementById("themes-button") const themesButton = document.getElementById("themes-button");
if (themesButton) { if (themesButton) {
const theme = await files.loadTheme() const theme = await files.loadTheme();
themesButton.textContent = theme[0] themesButton.textContent = theme[0];
} }
} }
function changeTheme(theme, state) { function changeTheme(theme, state) {
toggleThemesMenu() toggleThemesMenu();
const lacePinCheckbox = document.getElementById('lace-pin'); const lacePinCheckbox = document.getElementById("lace-pin");
if (lacePinCheckbox) { if (lacePinCheckbox) {
lacePinState = lacePinCheckbox.checked; lacePinState = lacePinCheckbox.checked;
} } else if (state) {
else if (state) { lacePinState = state;
lacePinState = state } else {
} lacePinState = false;
else {
lacePinState = false
} }
if (actualTheme[0] == theme && actualTheme[1] == lacePinState) { if (actualTheme[0] == theme && actualTheme[1] == lacePinState) {
return return;
} }
actualTheme = [theme, lacePinState] actualTheme = [theme, lacePinState];
files.saveTheme(theme, lacePinState) files.saveTheme(theme, lacePinState);
setThemeButton() setThemeButton();
// prettier-ignore
const themesColors = { const themesColors = {
"var": ["--primary-color", "--secondary-color", "--background-color"], "var": ["--primary-color", "--secondary-color", "--background-color"],
"Silksong": ["rgba(255, 25, 0, 0.3)", "#ff6b6b", "rgba(255, 72, 0, 0.2)"], "Silksong": ["rgba(255, 25, 0, 0.3)", "#ff6b6b", "rgba(255, 72, 0, 0.2)"],
@@ -426,17 +418,18 @@ function changeTheme(theme, state) {
"Surface": ["rgba(75, 120, 255, 0.3)", "#87c3ff", "rgba(42, 107, 203, 0.2)"], "Surface": ["rgba(75, 120, 255, 0.3)", "#87c3ff", "rgba(42, 107, 203, 0.2)"],
"Steel": ["rgba(164, 164, 164, 0.3)", "#c5b9b9", "rgba(255, 255, 255, 0.2)"] "Steel": ["rgba(164, 164, 164, 0.3)", "#c5b9b9", "rgba(255, 255, 255, 0.2)"]
} }
for(let i = 0; i < 3; i++) {
document.documentElement.style.setProperty(themesColors.var[i], themesColors[theme][i]) for (let i = 0; i < 3; i++) {
document.documentElement.style.setProperty(themesColors.var[i], themesColors[theme][i]);
} }
const backgroundVideo = document.getElementById("background-video") const backgroundVideo = document.getElementById("background-video");
let backgroundVideoPath = `assets/background/${theme}.mp4` let backgroundVideoPath = `assets/background/${theme}.mp4`;
if (lacePinState) { if (lacePinState) {
backgroundVideoPath = `assets/background/${theme} Lace Pin.mp4` backgroundVideoPath = `assets/background/${theme} Lace Pin.mp4`;
} }
backgroundVideo.src = backgroundVideoPath backgroundVideo.src = backgroundVideoPath;
} }
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
@@ -444,12 +437,12 @@ function changeTheme(theme, state) {
async function launch(mode) { async function launch(mode) {
await electronAPI.launchGame(mode); await electronAPI.launchGame(mode);
setBepinexVersion() setBepinexVersion();
} }
async function autoDetectGamePath() { async function autoDetectGamePath() {
await files.autoDetectGamePath() await files.autoDetectGamePath();
if (document.getElementById("silksong-path-input")) { if (document.getElementById("silksong-path-input")) {
document.getElementById("silksong-path-input").value = await files.loadSilksongPath() document.getElementById("silksong-path-input").value = await files.loadSilksongPath();
} }
} }

View File

@@ -2,7 +2,9 @@
margin: 0; margin: 0;
box-sizing: border-box; box-sizing: border-box;
font-family: "Segoe UI", sans-serif; font-family: "Segoe UI", sans-serif;
cursor: url("assets/cursor.png") 0 0, auto !important; cursor:
url("assets/cursor.png") 0 0,
auto !important;
} }
:root { :root {
@@ -13,7 +15,7 @@
--text-color: #eee; --text-color: #eee;
--transparent-black: rgba(0, 0, 0, 0.4); --transparent-black: rgba(0, 0, 0, 0.4);
--darker-transparent-black: rgba(0, 0, 0, 0.8); --darker-transparent-black: rgba(0, 0, 0, 0.8);
--transparent-grey: rgba(30, 30, 30, 0.8) --transparent-grey: rgba(30, 30, 30, 0.8);
} }
body { body {
@@ -69,6 +71,10 @@ body {
height: 100px; height: 100px;
} }
.invert-color {
filter: invert(1);
}
.nav { .nav {
padding: 20px; padding: 20px;
} }

View File

@@ -1,84 +1,87 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>Welcome to Silk Fly Launcher</title> <title>Welcome to Silk Fly Launcher</title>
<link rel="stylesheet" href="welcome.css" /> <link rel="stylesheet" href="welcome.css" />
<link rel="stylesheet" href="style.css" /> <link rel="stylesheet" href="style.css" />
</head> </head>
<body> <body>
<div class="app"> <div class="app">
<video autoplay muted loop class="background-video" id="background-video" src="assets/background/Silksong.mp4" type="video/mp4"></video> <video autoplay muted loop class="background-video" id="background-video" src="assets/background/Silksong.mp4" type="video/mp4"></video>
<div class="welcome-div" id="main-div"> <div class="welcome-div" id="main-div">
<br> <br />
<div id="page"></div> <div id="page"></div>
<div class="button-div" id="button-div"></div> <div class="button-div" id="button-div"></div>
</div>
</div>
<template id="welcome-template">
<h1 class="title">Welcome to Silk Fly Launcher</h1>
<div class="horizontal-div welcome-div">
<p>Silk Fly Launcher is a launcher and mod manager for Silksong mods from Nexus, built with Electron.</p>
<p>Made with ♥ by <a href="" class="link" onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar')">GabiZar</a>.</p>
</div>
</template>
<template id="one-button-template">
<button class="default-button bigger-button" onclick="initialImportData()">Import Data</button>
<button class="default-button bigger-button" onclick="next()">Next →</button>
</template>
<template id="two-button-template">
<button class="default-button bigger-button" onclick="back()">← Back</button>
<button class="default-button bigger-button" onclick="next()">Next →</button>
</template>
<template id="path-template">
<h1 class="title">Enter Hollow Knight: Silksong path</h1>
<p>Please verify the path to your game and edit it if the auto detection didn't work</p>
<div class="horizontal-div">
<label>Enter Silksong path: </label>
<input type="text" class="input" id="silksong-path-input" name="silksong-path-input">
<button class="default-button" onclick="autoDetectGamePath()">Auto Detect</button>
</div>
</template>
<template id="nexus-template">
<h1 class="title">Enter your nexus API key</h1>
<p>Please enter your nexus API key. To get your API key go to <a href="https://www.nexusmods.com/settings/api-keys" class="link" id="external-link">https://www.nexusmods.com/settings/api-keys</a> and click on new personnal API key</p>
<div class="horizontal-div">
<label for="nexus-api-label">Enter your nexus api: </label>
<input type="text" class="input" id="nexus-api-input" name="nexus-api-input">
<img class="nexus-check-image" id="nexus-check-image" src="assets/cross.svg">
<button class="default-button" onclick="verifyNexusAPI()">Verify</button>
</div>
</template>
<template id="style-template">
<h1 class="title">Chose the theme of the app</h1>
<div class="horizontal-div welcome-div">
<label>Themes: </label>
<div class="themes-div">
<div class="default-button longer-button" id="themes-button" onclick="toggleThemesMenu()">Silksong</div>
<div class="themes-menu longer-button" id="themes-menu">
<li onclick="changeTheme('Silksong')">Silksong</li>
<li onclick="changeTheme('Citadel of song')">Citadel of song</li>
<li onclick="changeTheme('Cradle')">Cradle</li>
<li onclick="changeTheme('Abyss')">Abyss</li>
<li onclick="changeTheme('Greyroot')">Greyroot</li>
<li onclick="changeTheme('Surface')">Surface</li>
<li onclick="changeTheme('Steel')">Steel</li>
</div>
</div> </div>
</div> </div>
</template>
<template id="tutorial-template"> <template id="welcome-template">
<h1 class="title">Tutorial to download mod from nexus</h1> <h1 class="title">Welcome to Silk Fly Launcher</h1>
</template> <div class="horizontal-div welcome-div">
<p>Silk Fly Launcher is a launcher and mod manager for Silksong mods from Nexus, built with Electron.</p>
<p>Made with ♥ by <a href="" class="link" onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar')">GabiZar</a>.</p>
</div>
</template>
<script src="renderer.js"></script> <template id="one-button-template">
</body> <button class="default-button bigger-button" onclick="initialImportData()">Import Data</button>
<button class="default-button bigger-button" onclick="next()">Next →</button>
</template>
<template id="two-button-template">
<button class="default-button bigger-button" onclick="back()">← Back</button>
<button class="default-button bigger-button" onclick="next()">Next →</button>
</template>
<template id="path-template">
<h1 class="title">Enter Hollow Knight: Silksong path</h1>
<p>Please verify the path to your game and edit it if the auto detection didn't work</p>
<div class="horizontal-div">
<label>Enter Silksong path: </label>
<input type="text" class="input" id="silksong-path-input" name="silksong-path-input" />
<button class="default-button" onclick="autoDetectGamePath()">Auto Detect</button>
</div>
</template>
<template id="nexus-template">
<h1 class="title">Enter your nexus API key</h1>
<p>
Please enter your nexus API key. To get your API key go to
<a href="https://www.nexusmods.com/settings/api-keys" class="link" id="external-link">https://www.nexusmods.com/settings/api-keys</a> and click on new personnal API key
</p>
<div class="horizontal-div">
<label for="nexus-api-label">Enter your nexus api: </label>
<input type="text" class="input" id="nexus-api-input" name="nexus-api-input" />
<img class="nexus-check-image" id="nexus-check-image" src="assets/cross.svg" />
<button class="default-button" onclick="verifyNexusAPI()">Verify</button>
</div>
</template>
<template id="style-template">
<h1 class="title">Chose the theme of the app</h1>
<div class="horizontal-div welcome-div">
<label>Themes: </label>
<div class="themes-div">
<div class="default-button longer-button" id="themes-button" onclick="toggleThemesMenu()">Silksong</div>
<div class="themes-menu longer-button" id="themes-menu">
<li onclick="changeTheme('Silksong')">Silksong</li>
<li onclick="changeTheme('Citadel of song')">Citadel of song</li>
<li onclick="changeTheme('Cradle')">Cradle</li>
<li onclick="changeTheme('Abyss')">Abyss</li>
<li onclick="changeTheme('Greyroot')">Greyroot</li>
<li onclick="changeTheme('Surface')">Surface</li>
<li onclick="changeTheme('Steel')">Steel</li>
</div>
</div>
</div>
</template>
<template id="tutorial-template">
<h1 class="title">Tutorial to download mod from nexus</h1>
</template>
<script src="renderer.js"></script>
</body>
</html> </html>