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

426
main.js
View File

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

View File

@@ -1,20 +1,21 @@
{
"name": "silkflylauncher",
"version": "1.0.0",
"description": "A launcher and manager for silksong bepinex mods written in javascript",
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "GabiZar",
"license": "SEE LICENSE IN LICENSE",
"devDependencies": {
"electron": "^39.2.7"
},
"dependencies": {
"@nexusmods/nexus-api": "^1.1.5",
"electron-store": "^11.0.2",
"extract-zip": "^2.0.1"
}
"name": "silkflylauncher",
"version": "1.0.0",
"description": "A launcher and manager for silksong bepinex mods written in javascript",
"main": "main.js",
"type": "module",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "GabiZar",
"license": "GPL-3.0",
"devDependencies": {
"electron": "^39.2.7"
},
"dependencies": {
"@nexusmods/nexus-api": "^1.1.5",
"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,
node: () => process.versions.node,
chromium: () => process.versions.chrome,
electron: () => process.versions.electron
electron: () => process.versions.electron,
});
contextBridge.exposeInMainWorld('files', {
delete: () => ipcRenderer.invoke('delete-data'),
export: () => ipcRenderer.invoke('export-data'),
import: () => ipcRenderer.invoke('import-data'),
contextBridge.exposeInMainWorld("files", {
delete: () => ipcRenderer.invoke("delete-data"),
export: () => ipcRenderer.invoke("export-data"),
import: () => ipcRenderer.invoke("import-data"),
autoDetectGamePath: () => ipcRenderer.invoke('auto-detect-game-path'),
saveSilksongPath: (path) => ipcRenderer.invoke('save-path', path),
loadSilksongPath: () => ipcRenderer.invoke('load-path'),
loadBepinexVersion: () => ipcRenderer.invoke('load-bepinex-version'),
loadBepinexBackupVersion: () => ipcRenderer.invoke('load-bepinex-backup-version'),
saveNexusAPI: (api) => ipcRenderer.invoke('save-nexus-api', api),
loadNexusAPI: () => ipcRenderer.invoke('load-nexus-api'),
saveTheme: (theme, lacePinState) => ipcRenderer.invoke('save-theme', theme, lacePinState),
loadTheme: () => ipcRenderer.invoke('load-theme')
autoDetectGamePath: () => ipcRenderer.invoke("auto-detect-game-path"),
saveSilksongPath: (path) => ipcRenderer.invoke("save-path", path),
loadSilksongPath: () => ipcRenderer.invoke("load-path"),
loadBepinexVersion: () => ipcRenderer.invoke("load-bepinex-version"),
loadBepinexBackupVersion: () => ipcRenderer.invoke("load-bepinex-backup-version"),
saveNexusAPI: (api) => ipcRenderer.invoke("save-nexus-api", api),
loadNexusAPI: () => ipcRenderer.invoke("load-nexus-api"),
saveTheme: (theme, lacePinState) => ipcRenderer.invoke("save-theme", theme, lacePinState),
loadTheme: () => ipcRenderer.invoke("load-theme"),
});
contextBridge.exposeInMainWorld('electronAPI', {
openExternalLink: (url) => ipcRenderer.invoke('open-link', url),
openWindow: (file) => ipcRenderer.invoke('open-window', file),
launchGame: (mode) => ipcRenderer.invoke('launch-game', mode),
loadMainPage: () => ipcRenderer.invoke('load-main-page'),
getPage: () => ipcRenderer.invoke('get-page')
contextBridge.exposeInMainWorld("electronAPI", {
openExternalLink: (url) => ipcRenderer.invoke("open-link", url),
openWindow: (file) => ipcRenderer.invoke("open-window", file),
launchGame: (mode) => ipcRenderer.invoke("launch-game", mode),
loadMainPage: () => ipcRenderer.invoke("load-main-page"),
getPage: () => ipcRenderer.invoke("get-page"),
});
contextBridge.exposeInMainWorld('bepinex', {
install: () => ipcRenderer.invoke('install-bepinex'),
uninstall: () => ipcRenderer.invoke('uninstall-bepinex'),
backup: () => ipcRenderer.invoke('backup-bepinex'),
deleteBackup: () => ipcRenderer.invoke('delete-bepinex-backup')
})
contextBridge.exposeInMainWorld("bepinex", {
install: () => ipcRenderer.invoke("install-bepinex"),
uninstall: () => ipcRenderer.invoke("uninstall-bepinex"),
backup: () => ipcRenderer.invoke("backup-bepinex"),
deleteBackup: () => ipcRenderer.invoke("delete-bepinex-backup"),
});
contextBridge.exposeInMainWorld('nexus', {
verifyAPI: () => ipcRenderer.invoke('verify-nexus-api'),
getLatestMods: () => ipcRenderer.invoke('get-latest-mods'),
download: (link) => ipcRenderer.invoke('download-mod', link)
})
contextBridge.exposeInMainWorld("nexus", {
verifyAPI: () => ipcRenderer.invoke("verify-nexus-api"),
getLatestMods: () => ipcRenderer.invoke("get-latest-mods"),
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">
<head>
<meta charset="UTF-8" />
<title>Silk Fly Launcher</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<head>
<meta charset="UTF-8" />
<title>Silk Fly Launcher</title>
<link rel="stylesheet" href="style.css" />
</head>
<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">
<video autoplay muted loop class="background-video" id="background-video" src="assets/background/Silksong.mp4" type="video/mp4"></video>
<!-- Sidebar -->
<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
<!-- Sidebar -->
<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</h5>
</div>
<div class="nav-section">
<span class="nav-title">Mods</span>
<button onclick="navigate('mods-installed')">
<img src="installed_mods_icon.png" class="button-icon"/>
Installed
<button onclick="navigate('mods-online')">
<img src="online_mods_icon.png" class="button-icon"/>
Online
</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>
<button onclick="launch('modded')">
<img src="modded_launch_icon.png" class="button-icon" />
Run Modded
</button>
</div>
<div class="nav-section">
<span class="nav-title">Settings</span>
<button onclick="navigate('general-settings')">
<img src="general_settings_icon.png" class="button-icon"/>
General
</div>
</nav>
</aside>
<div class="nav-section">
<span class="nav-title">Mods</span>
<button onclick="navigate('mods-installed')">
<img src="installed_mods_icon.png" class="button-icon" />
Installed
</button>
<button onclick="navigate('mods-online')">
<img src="online_mods_icon.png" class="button-icon" />
Online
</button>
</div>
<!-- Main content -->
<main class="content">
<h1 id="title">Silk Fly Launcher</h1>
<div class="view" id="view">
</div>
</main>
</div>
<div class="nav-section">
<span class="nav-title">Settings</span>
<button onclick="navigate('general-settings')">
<img src="general_settings_icon.png" class="button-icon" />
General
</button>
</div>
</nav>
</aside>
<!-- Template -->
<template id="home-template">
<h2>About</h2>
<div class="horizontal-div separated-div">
<div class="horizontal-div">
<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>
<!-- Main content -->
<main class="content">
<h1 id="title">Silk Fly Launcher</h1>
<div class="view" id="view"></div>
</main>
</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">
<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>
<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="assets/github.svg" alt="Github logo" class="logo-img invert-color" />
</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>
<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>
<p id="version-text"></p>
</div>
</template>
<br />
</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>
</html>
<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>

View File

@@ -8,13 +8,13 @@ const onlineModsTemplate = document.getElementById("online-mods-template");
const settingsTemplate = document.getElementById("settings-template");
const modTemplate = document.getElementById("mod-template");
let oldPage
let actualTheme = []
let oldPage;
let actualTheme = [];
//////////////////////////////////////////////////////
///////////////// CONST FOR WELCOME //////////////////
let actualPage = 0
let actualPage = 0;
const pageDiv = document.getElementById("page");
const buttonDiv = document.getElementById("button-div");
@@ -31,16 +31,15 @@ const tutorialTemplate = document.getElementById("tutorial-template");
//////////////////////////////////////////////////////
////////////////////// STARTUP ///////////////////////
on_startup()
on_startup();
async function on_startup() {
if (await electronAPI.getPage() == "index.html") {
const theme = await files.loadTheme()
changeTheme(theme[0], theme[1])
navigate("home")
}
else if (await electronAPI.getPage() == "welcome.html") {
welcomeNavigate()
if ((await electronAPI.getPage()) == "index.html") {
const theme = await files.loadTheme();
changeTheme(theme[0], theme[1]);
navigate("home");
} else if ((await electronAPI.getPage()) == "welcome.html") {
welcomeNavigate();
}
}
@@ -49,223 +48,220 @@ async function on_startup() {
async function navigate(page) {
if (oldPage == page) {
return
return;
}
oldPage = page
oldPage = page;
view.replaceChildren()
view.replaceChildren();
switch (page) {
case "home":
title.innerText = "Silk Fly Launcher";
const HomeTemplateCopy = HomeTemplate.content.cloneNode(true)
view.appendChild(HomeTemplateCopy)
const HomeTemplateCopy = HomeTemplate.content.cloneNode(true);
view.appendChild(HomeTemplateCopy);
break;
case "mods-installed":
title.innerText = "Installed Mods";
const installedModsTemplateCopy = installedModsTemplate.content.cloneNode(true)
const searchFormInstalled = installedModsTemplateCopy.getElementById("search-form")
searchFormInstalled.addEventListener('submit', async function(event) {
event.preventDefault()
})
const installedModsTemplateCopy = installedModsTemplate.content.cloneNode(true);
const searchFormInstalled = installedModsTemplateCopy.getElementById("search-form");
view.appendChild(installedModsTemplateCopy)
searchFormInstalled.addEventListener("submit", async function (event) {
event.preventDefault();
});
view.appendChild(installedModsTemplateCopy);
break;
case "mods-online":
title.innerText = "Online Mods";
const onlineModsTemplateCopy = onlineModsTemplate.content.cloneNode(true)
const ModsContainer = onlineModsTemplateCopy.getElementById("mods-container")
const searchFormNexus = onlineModsTemplateCopy.getElementById("search-form")
searchFormNexus.addEventListener('submit', async function(event) {
event.preventDefault()
})
const onlineModsTemplateCopy = onlineModsTemplate.content.cloneNode(true);
const ModsContainer = onlineModsTemplateCopy.getElementById("mods-container");
const searchFormNexus = onlineModsTemplateCopy.getElementById("search-form");
view.appendChild(onlineModsTemplateCopy)
searchFormNexus.addEventListener("submit", async function (event) {
event.preventDefault();
});
mods = await nexus.getLatestMods()
view.appendChild(onlineModsTemplateCopy);
mods = await nexus.getLatestMods();
if (mods == undefined) {
break;
}
for(const mod of mods) {
for (const mod of mods) {
if (mod.name == undefined) {
continue
continue;
}
const modTemplateCopy = modTemplate.content.cloneNode(true)
const modTemplateCopy = modTemplate.content.cloneNode(true);
if (mod.name) {
const modTitleText = modTemplateCopy.getElementById("mod-title")
modTitleText.innerText = mod.name
const modTitleText = modTemplateCopy.getElementById("mod-title");
modTitleText.innerText = mod.name;
}
if (mod.author) {
const modAuthorText = modTemplateCopy.getElementById("mod-author")
modAuthorText.innerText = `by ${mod.author}`
const modAuthorText = modTemplateCopy.getElementById("mod-author");
modAuthorText.innerText = `by ${mod.author}`;
}
if (mod.endorsement_count) {
const modEndorsementsNumber = modTemplateCopy.getElementById("mod-endorsements-number")
const modEndorsementsNumber = modTemplateCopy.getElementById("mod-endorsements-number");
if (mod.endorsement_count > 1) {
modEndorsementsNumber.innerText = `${mod.endorsement_count} likes`
}
else {
modEndorsementsNumber.innerText = `${mod.endorsement_count} like`
modEndorsementsNumber.innerText = `${mod.endorsement_count} likes`;
} else {
modEndorsementsNumber.innerText = `${mod.endorsement_count} like`;
}
}
if (mod.summary) {
const modDescriptionText = modTemplateCopy.getElementById("mod-description")
modDescriptionText.innerText = mod.summary
const modDescriptionText = modTemplateCopy.getElementById("mod-description");
modDescriptionText.innerText = mod.summary;
}
if (mod.picture_url) {
const modPicture = modTemplateCopy.getElementById("mod-icon")
modPicture.src = mod.picture_url
const modPicture = modTemplateCopy.getElementById("mod-icon");
modPicture.src = mod.picture_url;
}
if (mod.version && mod.updated_timestamp) {
const modVersionText = modTemplateCopy.getElementById("mod-version")
modVersionText.innerText = `V${mod.version} last updated on ${mod.updated_time.slice(0, 10)}`
const modVersionText = modTemplateCopy.getElementById("mod-version");
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")
modLinkButton.href = modUrl
modLinkButton.addEventListener('click', function(event) {
event.preventDefault()
const modLink = modLinkButton.href
electronAPI.openExternalLink(modLink)
})
const modLinkButton = modTemplateCopy.getElementById("external-link");
modLinkButton.href = modUrl;
modLinkButton.addEventListener("click", function (event) {
event.preventDefault();
const modLink = modLinkButton.href;
electronAPI.openExternalLink(modLink);
});
modDownloadButton = modTemplateCopy.getElementById("download-mod-button")
modDownloadButton.addEventListener('click', function(event) {
event.preventDefault()
const modDownloadLink = `${modUrl}?tab=files`
nexus.download(modDownloadLink)
})
modDownloadButton = modTemplateCopy.getElementById("download-mod-button");
modDownloadButton.addEventListener("click", function (event) {
event.preventDefault();
const modDownloadLink = `${modUrl}?tab=files`;
nexus.download(modDownloadLink);
});
ModsContainer.appendChild(modTemplateCopy)
ModsContainer.appendChild(modTemplateCopy);
}
break;
case "general-settings":
title.innerText = "Settings";
const settingsTemplateCopy = settingsTemplate.content.cloneNode(true)
const silksongPathInput = settingsTemplateCopy.getElementById("silksong-path-input")
const nexusAPIInput = settingsTemplateCopy.getElementById("nexus-api-input")
const versionsList = settingsTemplateCopy.getElementById("versions-list")
const settingsTemplateCopy = settingsTemplate.content.cloneNode(true);
const silksongPathInput = settingsTemplateCopy.getElementById("silksong-path-input");
const nexusAPIInput = settingsTemplateCopy.getElementById("nexus-api-input");
const versionsList = settingsTemplateCopy.getElementById("versions-list");
const versionsDictionnary = {
"Silk-Fly-Launcher": `Silk Fly Launcher: v${versions.silkFlyLauncher()}`,
"Electron": `Electron: v${versions.electron()}`,
"Node": `Node.js: v${versions.node()}`,
"Chromium": `Chromium: v${versions.chromium()}`,
}
const lacePinCheckbox = settingsTemplateCopy.getElementById('lace-pin');
"Silk-Fly-Launcher": `Silk Fly Launcher: v${versions.silkFlyLauncher()}`,
Electron: `Electron: v${versions.electron()}`,
Node: `Node.js: v${versions.node()}`,
Chromium: `Chromium: v${versions.chromium()}`,
};
const lacePinCheckbox = settingsTemplateCopy.getElementById("lace-pin");
silksongPathInput.value = await files.loadSilksongPath()
silksongPathInput.addEventListener('input', async function(event) {
let silksongPath = silksongPathInput.value
files.saveSilksongPath(silksongPath)
silksongPathInput.value = await files.loadSilksongPath();
silksongPathInput.addEventListener("input", async function (event) {
let silksongPath = silksongPathInput.value;
files.saveSilksongPath(silksongPath);
});
nexusAPIInput.value = await files.loadNexusAPI()
nexusAPIInput.addEventListener('input', async function(event) {
let nexusAPI = nexusAPIInput.value
files.saveNexusAPI(nexusAPI)
nexusAPIInput.value = await files.loadNexusAPI();
nexusAPIInput.addEventListener("input", async function (event) {
let nexusAPI = nexusAPIInput.value;
files.saveNexusAPI(nexusAPI);
});
for(const element of versionsList.children) {
element.innerText = versionsDictionnary[element.id]
for (const element of versionsList.children) {
element.innerText = versionsDictionnary[element.id];
}
const theme = await files.loadTheme()
lacePinCheckbox.checked = theme[1]
const theme = await files.loadTheme();
lacePinCheckbox.checked = theme[1];
lacePinCheckbox.addEventListener('change', async function() {
lacePinCheckbox.addEventListener("change", async function () {
if (this.checked) {
const theme = await files.loadTheme();
changeTheme(theme[0], true);
toggleThemesMenu()
}
else {
toggleThemesMenu();
} else {
const theme = await files.loadTheme();
changeTheme(theme[0], false);
toggleThemesMenu()
toggleThemesMenu();
}
});
view.appendChild(settingsTemplateCopy)
setBepinexVersion()
setThemeButton()
verifyNexusAPI()
view.appendChild(settingsTemplateCopy);
setBepinexVersion();
setThemeButton();
verifyNexusAPI();
break;
}
}
async function welcomeNavigate() {
pageDiv.replaceChildren()
pageDiv.replaceChildren();
switch (actualPage) {
case 0:
pageDiv.appendChild(welcomeTemplate.content.cloneNode(true))
buttonDiv.replaceChildren()
buttonDiv.appendChild(oneButtonTemplate.content.cloneNode(true))
pageDiv.appendChild(welcomeTemplate.content.cloneNode(true));
buttonDiv.replaceChildren();
buttonDiv.appendChild(oneButtonTemplate.content.cloneNode(true));
break;
case 1:
pageDiv.appendChild(silksongPathTemplate.content.cloneNode(true))
buttonDiv.replaceChildren()
buttonDiv.appendChild(twoButtonTemplate.content.cloneNode(true))
pageDiv.appendChild(silksongPathTemplate.content.cloneNode(true));
buttonDiv.replaceChildren();
buttonDiv.appendChild(twoButtonTemplate.content.cloneNode(true));
const silksongPathInput = document.getElementById("silksong-path-input")
if (await files.loadSilksongPath() == "") {
autoDetectGamePath()
}
else {
document.getElementById("silksong-path-input").value = await files.loadSilksongPath()
const silksongPathInput = document.getElementById("silksong-path-input");
if ((await files.loadSilksongPath()) == "") {
autoDetectGamePath();
} else {
document.getElementById("silksong-path-input").value = await files.loadSilksongPath();
}
silksongPathInput.addEventListener('input', async function(event) {
let silksongPath = silksongPathInput.value
await files.saveSilksongPath(silksongPath)
silksongPathInput.addEventListener("input", async function (event) {
let silksongPath = silksongPathInput.value;
await files.saveSilksongPath(silksongPath);
});
break;
case 2:
pageDiv.appendChild(nexusTemplate.content.cloneNode(true))
const nexusLink = document.getElementById("external-link")
nexusLink.addEventListener('click', function(event) {
event.preventDefault()
const url = nexusLink.href
electronAPI.openExternalLink(url)
})
pageDiv.appendChild(nexusTemplate.content.cloneNode(true));
const nexusLink = document.getElementById("external-link");
nexusLink.addEventListener("click", function (event) {
event.preventDefault();
const url = nexusLink.href;
electronAPI.openExternalLink(url);
});
const nexusAPIInput = document.getElementById("nexus-api-input")
nexusAPIInput.value = await files.loadNexusAPI()
nexusAPIInput.addEventListener('input', async function(event) {
let nexusAPI = nexusAPIInput.value
await files.saveNexusAPI(nexusAPI)
const nexusAPIInput = document.getElementById("nexus-api-input");
nexusAPIInput.value = await files.loadNexusAPI();
nexusAPIInput.addEventListener("input", async function (event) {
let nexusAPI = nexusAPIInput.value;
await files.saveNexusAPI(nexusAPI);
});
break;
case 3:
pageDiv.appendChild(styleTemplate.content.cloneNode(true))
pageDiv.appendChild(styleTemplate.content.cloneNode(true));
break;
case 4:
pageDiv.appendChild(tutorialTemplate.content.cloneNode(true))
pageDiv.appendChild(tutorialTemplate.content.cloneNode(true));
break;
case 5:
electronAPI.loadMainPage()
electronAPI.loadMainPage();
break;
}
}
function next() {
actualPage++
welcomeNavigate()
actualPage++;
welcomeNavigate();
}
function back() {
actualPage--
welcomeNavigate()
actualPage--;
welcomeNavigate();
}
//////////////////////////////////////////////////////
@@ -273,149 +269,145 @@ function back() {
async function initialImportData() {
if (await files.import()) {
electronAPI.loadMainPage()
electronAPI.loadMainPage();
}
}
async function importData() {
await files.import()
document.getElementById("silksong-path-input").value = await files.loadSilksongPath()
document.getElementById("nexus-api-input").value = await files.loadNexusAPI()
const lacePinCheckbox = document.getElementById('lace-pin')
const theme = await files.loadTheme()
lacePinCheckbox.checked = theme[1]
changeTheme(theme[0])
toggleThemesMenu()
await files.import();
document.getElementById("silksong-path-input").value = await files.loadSilksongPath();
document.getElementById("nexus-api-input").value = await files.loadNexusAPI();
const lacePinCheckbox = document.getElementById("lace-pin");
const theme = await files.loadTheme();
lacePinCheckbox.checked = theme[1];
changeTheme(theme[0]);
toggleThemesMenu();
}
async function exportData() {
await files.export()
await files.export();
}
async function deleteData() {
const lacePinCheckbox = document.getElementById('lace-pin')
lacePinCheckbox.checked = false
changeTheme("Silksong")
toggleThemesMenu()
await files.delete()
document.getElementById("silksong-path-input").value = await files.loadSilksongPath()
document.getElementById("nexus-api-input").value = await files.loadNexusAPI()
const lacePinCheckbox = document.getElementById("lace-pin");
lacePinCheckbox.checked = false;
changeTheme("Silksong");
toggleThemesMenu();
await files.delete();
document.getElementById("silksong-path-input").value = await files.loadSilksongPath();
document.getElementById("nexus-api-input").value = await files.loadNexusAPI();
}
//////////////////////////////////////////////////////
////////////////////// BEPINEX ///////////////////////
async function installBepinex() {
await bepinex.install()
setBepinexVersion()
await bepinex.install();
setBepinexVersion();
}
async function uninstallBepinex() {
await bepinex.uninstall()
setBepinexVersion()
await bepinex.uninstall();
setBepinexVersion();
}
async function backupBepinex() {
await bepinex.backup()
setBepinexVersion()
await bepinex.backup();
setBepinexVersion();
}
async function deleteBepinexBackup() {
await bepinex.deleteBackup()
setBepinexVersion()
await bepinex.deleteBackup();
setBepinexVersion();
}
async function setBepinexVersion() {
const bepinexVersionText = document.getElementById("bepinex-version-text")
const bepinexVersionText = document.getElementById("bepinex-version-text");
if (bepinexVersionText == undefined) {
return
return;
}
const bepinexVersion = await files.loadBepinexVersion()
const bepinexBackupVersion = await files.loadBepinexBackupVersion()
if(bepinexVersion == undefined) {
if(bepinexBackupVersion == undefined) {
bepinexVersionText.innerText = "BepInEx is not installed"
const bepinexVersion = await files.loadBepinexVersion();
const bepinexBackupVersion = await files.loadBepinexBackupVersion();
if (bepinexVersion == undefined) {
if (bepinexBackupVersion == undefined) {
bepinexVersionText.innerText = "BepInEx is not installed";
} else {
bepinexVersionText.innerText = `BepInEx ${bepinexBackupVersion} is backed up`;
}
else {
bepinexVersionText.innerText = `BepInEx ${bepinexBackupVersion} is backed up`
}
}
else {
bepinexVersionText.innerText = `BepInEx ${bepinexVersion} is installed`
} else {
bepinexVersionText.innerText = `BepInEx ${bepinexVersion} is installed`;
}
}
async function searchInstalledMods() {
const searchInput = document.getElementById("search-input")
console.log(searchInput.value)
const searchInput = document.getElementById("search-input");
console.log(searchInput.value);
}
//////////////////////////////////////////////////////
/////////////////////// NEXUS ////////////////////////
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) {
return
return;
}
if (response) {
nexusCheckImage.src = "assets/check.svg"
}
else {
nexusCheckImage.src = "assets/cross.svg"
nexusCheckImage.src = "assets/check.svg";
} else {
nexusCheckImage.src = "assets/cross.svg";
}
}
async function searchNexusMods() {
const searchInput = document.getElementById("search-input")
console.log(searchInput.value)
const searchInput = document.getElementById("search-input");
console.log(searchInput.value);
}
//////////////////////////////////////////////////////
/////////////////////// THEMES ///////////////////////
function toggleThemesMenu() {
const themesMenu = document.getElementById("themes-menu")
const themesMenu = document.getElementById("themes-menu");
if (themesMenu) {
themesMenu.classList.toggle("show")
themesMenu.classList.toggle("show");
}
}
async function setThemeButton() {
const themesButton = document.getElementById("themes-button")
const themesButton = document.getElementById("themes-button");
if (themesButton) {
const theme = await files.loadTheme()
themesButton.textContent = theme[0]
const theme = await files.loadTheme();
themesButton.textContent = theme[0];
}
}
function changeTheme(theme, state) {
toggleThemesMenu()
toggleThemesMenu();
const lacePinCheckbox = document.getElementById('lace-pin');
const lacePinCheckbox = document.getElementById("lace-pin");
if (lacePinCheckbox) {
lacePinState = lacePinCheckbox.checked;
}
else if (state) {
lacePinState = state
}
else {
lacePinState = false
} else if (state) {
lacePinState = state;
} else {
lacePinState = false;
}
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 = {
"var": ["--primary-color", "--secondary-color", "--background-color"],
"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)"],
"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")
let backgroundVideoPath = `assets/background/${theme}.mp4`
const backgroundVideo = document.getElementById("background-video");
let backgroundVideoPath = `assets/background/${theme}.mp4`;
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) {
await electronAPI.launchGame(mode);
setBepinexVersion()
setBepinexVersion();
}
async function autoDetectGamePath() {
await files.autoDetectGamePath()
await files.autoDetectGamePath();
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;
box-sizing: border-box;
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 {
@@ -13,7 +15,7 @@
--text-color: #eee;
--transparent-black: rgba(0, 0, 0, 0.4);
--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 {
@@ -69,6 +71,10 @@ body {
height: 100px;
}
.invert-color {
filter: invert(1);
}
.nav {
padding: 20px;
}
@@ -86,7 +92,7 @@ body {
margin-bottom: 10px;
padding: 0 4px 4px;
border-bottom: 1px solid var(--secondary-color);
border-bottom: 1px solid var(--secondary-color);
}
.nav button {
@@ -124,7 +130,7 @@ body {
margin-bottom: 20px;
color: var(--text-color);
padding: 0 4px 4px;
border-bottom: 1px solid var(--secondary-color);
border-bottom: 1px solid var(--secondary-color);
}
.view {
@@ -135,7 +141,7 @@ body {
z-index: 0;
box-shadow: 0 0 50px var(--darker-transparent-black);
overflow: auto;
height: 90%;
height: 90%;
}
.horizontal-div {
@@ -371,4 +377,4 @@ body {
border: solid var(--text-color);
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
}

View File

@@ -1,84 +1,87 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Welcome to Silk Fly Launcher</title>
<link rel="stylesheet" href="welcome.css" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="app">
<video autoplay muted loop class="background-video" id="background-video" src="assets/background/Silksong.mp4" type="video/mp4"></video>
<head>
<meta charset="UTF-8" />
<title>Welcome to Silk Fly Launcher</title>
<link rel="stylesheet" href="welcome.css" />
<link rel="stylesheet" href="style.css" />
</head>
<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="welcome-div" id="main-div">
<br>
<div id="page"></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 class="welcome-div" id="main-div">
<br />
<div id="page"></div>
<div class="button-div" id="button-div"></div>
</div>
</div>
</template>
<template id="tutorial-template">
<h1 class="title">Tutorial to download mod from nexus</h1>
</template>
<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>
<script src="renderer.js"></script>
</body>
</html>
<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>
</template>
<template id="tutorial-template">
<h1 class="title">Tutorial to download mod from nexus</h1>
</template>
<script src="renderer.js"></script>
</body>
</html>