change indentation and add import config

This commit is contained in:
2026-01-19 12:23:16 +01:00
parent 0b5ee51ebd
commit 11ac159909
5 changed files with 322 additions and 303 deletions

91
main.js
View File

@@ -7,74 +7,89 @@ const store = new Store();
const userSavePath = app.getPath('userData') const userSavePath = app.getPath('userData')
const createWindow = () => { const createWindow = () => {
const win = new BrowserWindow({ const win = new BrowserWindow({
width: 1280, width: 1280,
height: 720, height: 720,
webPreferences: { webPreferences: {
preload: path.join(__dirname, 'preload.js') preload: path.join(__dirname, 'preload.js')
} }
}) })
win.loadFile('renderer/index.html') win.loadFile('renderer/index.html')
} }
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()
} }
}) })
ipcMain.handle('save-path', (event, path) => { ipcMain.handle('save-path', (event, path) => {
store.set('silksong-path', path); store.set('silksong-path', path);
}); });
ipcMain.handle('load-path', () => { ipcMain.handle('load-path', () => {
return store.get('silksong-path'); return store.get('silksong-path');
}); });
async function fileExists(filePath) { async function fileExists(filePath) {
try { try {
await fs.access(filePath); await fs.access(filePath);
return true; return true;
} catch { } catch {
return false; return false;
} }
} }
ipcMain.handle('file-exists', async (_, filePath) => { ipcMain.handle('file-exists', async (_, filePath) => {
return await fileExists(filePath); return await fileExists(filePath);
}); });
ipcMain.handle('get-userSavePath', () => { ipcMain.handle('get-userSavePath', () => {
return userSavePath return userSavePath
}); });
ipcMain.handle('delete-data', async (event, path) => { ipcMain.handle('delete-data', async (event, path) => {
await fs.unlink(path) await fs.unlink(path)
}); });
ipcMain.handle('export-data', async () => { ipcMain.handle('export-data', async () => {
const dataPath = `${userSavePath}\\config.json` const dataPath = `${userSavePath}\\config.json`
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 () => {
const dataPath = `${userSavePath}\\config.json`
const { canceled, filePaths } = await dialog.showOpenDialog({
title: 'Import Data',
properties: ['openFile'],
filters: [{ name: 'JSON', extensions: ['json'] }]
})
if (canceled || !filePaths) return
await fs.unlink(dataPath)
await fs.copyFile(filePaths[0], dataPath,fs.constants.COPYFILE_EXCL)
}) })

View File

@@ -1,22 +1,20 @@
const { contextBridge, ipcRenderer } = require('electron') const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('versions', { contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node, node: () => process.versions.node,
chrome: () => process.versions.chrome, chrome: () => process.versions.chrome,
electron: () => process.versions.electron electron: () => process.versions.electron
}); });
contextBridge.exposeInMainWorld('save', { contextBridge.exposeInMainWorld('save', {
saveSilksongPath: (path) => saveSilksongPath: (path) => ipcRenderer.invoke('save-path', path),
ipcRenderer.invoke('save-path', path), loadSilksongPath: () => ipcRenderer.invoke('load-path')
loadSilksongPath: () =>
ipcRenderer.invoke('load-path')
}); });
contextBridge.exposeInMainWorld('files', { contextBridge.exposeInMainWorld('files', {
fileExists: (path) => ipcRenderer.invoke('file-exists', path), fileExists: (path) => ipcRenderer.invoke('file-exists', path),
userSavePath: () => ipcRenderer.invoke('get-userSavePath'), userSavePath: () => ipcRenderer.invoke('get-userSavePath'),
delete: (path) => ipcRenderer.invoke('delete-data', path), delete: (path) => ipcRenderer.invoke('delete-data', path),
export: () => ipcRenderer.invoke('export-data'), export: () => ipcRenderer.invoke('export-data'),
import: (data) => ipcRenderer.invoke('import-data', data) import: () => ipcRenderer.invoke('import-data')
}); });

View File

@@ -1,102 +1,102 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
<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"> <div class="app">
<video autoplay muted loop id="menu_wallpaper" class="background_video"> <video autoplay muted loop id="menu_wallpaper" class="background_video">
<source src="assets/background.mp4" type="video/mp4"> <source src="assets/background.mp4" type="video/mp4">
</video> </video>
<!-- Sidebar --> <!-- Sidebar -->
<aside class="sidebar"> <aside class="sidebar">
<div class="logo" onclick="navigate('home')"> <div class="logo" onclick="navigate('home')">
<img src="assets/logo.png" alt="Silk Fly Launcher Logo" class="logo_img"/> <img src="assets/logo.png" alt="Silk Fly Launcher Logo" class="logo_img"/>
<h5 class="logo_title">Silk Fly Launcher</h1> <h5 class="logo_title">Silk Fly Launcher</h1>
</div> </div>
<nav class="nav"> <nav class="nav">
<div class="nav-section"> <div class="nav-section">
<span class="nav-title">Execute Silksong</span> <span class="nav-title">Execute Silksong</span>
<button onclick="launch('vanilla')"> <button onclick="launch('vanilla')">
<img src="vanilla_launch_icon.png" class="button_icon"/> <img src="vanilla_launch_icon.png" class="button_icon"/>
Run Vanilla Run Vanilla
<button onclick="launch('modded')"> <button onclick="launch('modded')">
<img src="modded_launch_icon.png" class="button_icon"/> <img src="modded_launch_icon.png" class="button_icon"/>
Run Modded Run Modded
</div> </div>
<div class="nav-section"> <div class="nav-section">
<span class="nav-title">Mods</span> <span class="nav-title">Mods</span>
<button onclick="navigate('mods-installed')"> <button onclick="navigate('mods-installed')">
<img src="installed_mods_icon.png" class="button_icon"/> <img src="installed_mods_icon.png" class="button_icon"/>
Installed Installed
<button onclick="navigate('mods-online')"> <button onclick="navigate('mods-online')">
<img src="online_mods_icon.png" class="button_icon"/> <img src="online_mods_icon.png" class="button_icon"/>
Online Online
</div> </div>
<div class="nav-section"> <div class="nav-section">
<span class="nav-title">Settings</span> <span class="nav-title">Settings</span>
<button onclick="navigate('general-settings')"> <button onclick="navigate('general-settings')">
<img src="general_settings_icon.png" class="button_icon"/> <img src="general_settings_icon.png" class="button_icon"/>
General General
</div> </div>
</nav> </nav>
</aside> </aside>
<!-- Main content --> <!-- Main content -->
<main class="content"> <main class="content">
<h1 id="title">Home</h1> <h1 id="title">Home</h1>
<div class="view" id="view"> <div class="view" id="view">
</div> </div>
</div> </div>
<div class="slider-container"> <div class="slider-container">
<label for="heightSlider">Height :</label> <label for="heightSlider">Height :</label>
<input type="range" id="heightSlider"> <input type="range" id="heightSlider">
</div> </div>
</div>
<!-- Template -->
<template id="home-template">
<h2>Silk Fly Launcher</h2>
<h3> Debugging data</h3>
<p id="version-text"></p>
</template>
<template id="installed-mods-template">
<p>List of installed mods.</p>
</template>
<template id="online-mods-template">
<p>Browse Nexus mods.</p>
</template>
<template id="settings-template">
<h2>General settings</h2>
<div class="horizontal-div">
<label for="silksong-path-label">Enter Silksong path: </label>
<input type="text" class="silksong-path-input" id="silksong-path-input" name="silksong-path-input">
<button class="default-button" onclick="autoDetectGamePath()">Auto Detect</button>
</div> </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>
</template>
<script src="renderer.js"></script> <!-- Template -->
<template id="home-template">
<h2>Silk Fly Launcher</h2>
<h3> Debugging data</h3>
<p id="version-text"></p>
</template>
<template id="installed-mods-template">
<p>List of installed mods.</p>
</template>
<template id="online-mods-template">
<p>Browse Nexus mods.</p>
</template>
<template id="settings-template">
<h2>General settings</h2>
<div class="horizontal-div">
<label for="silksong-path-label">Enter Silksong path: </label>
<input type="text" class="silksong-path-input" id="silksong-path-input" name="silksong-path-input">
<button class="default-button" onclick="autoDetectGamePath()">Auto Detect</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>
</template>
<script src="renderer.js"></script>
</body> </body>
</html> </html>

View File

@@ -12,70 +12,76 @@ navigate("home")
let savePath let savePath
files.userSavePath().then(path => { files.userSavePath().then(path => {
savePath = `${path}\\config.json` savePath = `${path}\\config.json`
files.fileExists(savePath).then(result => { files.fileExists(savePath).then(result => {
if(!result) { if(!result) {
autoDetectGamePath() autoDetectGamePath()
} }
}); });
}); });
async function navigate(page) { async function navigate(page) {
view.replaceChildren() view.replaceChildren()
switch (page) { switch (page) {
case "home": case "home":
title.innerText = "Home"; title.innerText = "Home";
const HomeTemplateCopy = HomeTemplate.content.cloneNode(true) const HomeTemplateCopy = HomeTemplate.content.cloneNode(true)
const versionText = HomeTemplateCopy.getElementById("version-text") const versionText = HomeTemplateCopy.getElementById("version-text")
versionText.innerText = versionText.innerText =
`Chrome version: (v${versions.chrome()}), ` + `Chrome version: (v${versions.chrome()}), ` +
`Node.js version: (v${versions.node()}), Electron version: (v${versions.electron()})` `Node.js version: (v${versions.node()}), Electron version: (v${versions.electron()})`
view.appendChild(HomeTemplateCopy) view.appendChild(HomeTemplateCopy)
break; break;
case "mods-installed": case "mods-installed":
const installedModsTemplateCopy = installedModsTemplate.content.cloneNode(true) const installedModsTemplateCopy = installedModsTemplate.content.cloneNode(true)
view.appendChild(installedModsTemplateCopy) view.appendChild(installedModsTemplateCopy)
break; break;
case "mods-online": case "mods-online":
const onlineModsTemplateCopy = onlineModsTemplate.content.cloneNode(true) const onlineModsTemplateCopy = onlineModsTemplate.content.cloneNode(true)
view.appendChild(onlineModsTemplateCopy) view.appendChild(onlineModsTemplateCopy)
break; break;
case "general-settings": case "general-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")
silksongPathInput.value = await save.loadSilksongPath() silksongPathInput.value = await save.loadSilksongPath()
silksongPathInput.addEventListener('input', async function(event) { silksongPathInput.addEventListener('input', async function(event) {
let silksongPath = silksongPathInput.value let silksongPath = silksongPathInput.value
await save.saveSilksongPath(silksongPath) await save.saveSilksongPath(silksongPath)
}); });
view.appendChild(settingsTemplateCopy) view.appendChild(settingsTemplateCopy)
} }
} }
function launch(mode) { function launch(mode) {
alert(`Launching the game in ${mode} mode.`); alert(`Launching the game in ${mode} mode.`);
} }
async function autoDetectGamePath() { async function autoDetectGamePath() {
const defaultSilksongPath = "C:/Program Files (x86)/Steam/steamapps/common/Hollow Knight Silksong/Hollow Knight Silksong.exe" const defaultSilksongPath = "C:/Program Files (x86)/Steam/steamapps/common/Hollow Knight Silksong/Hollow Knight Silksong.exe"
if (await files.fileExists(defaultSilksongPath)) { if (await files.fileExists(defaultSilksongPath)) {
await save.saveSilksongPath(defaultSilksongPath) await save.saveSilksongPath(defaultSilksongPath)
if (document.getElementById("silksong-path-input")) { if (document.getElementById("silksong-path-input")) {
document.getElementById("silksong-path-input").value = await save.loadSilksongPath() document.getElementById("silksong-path-input").value = await save.loadSilksongPath()
}
} }
}
} }
async function deleteData() { async function deleteData() {
await files.delete(savePath) await files.delete(savePath)
document.getElementById("silksong-path-input").value = await save.loadSilksongPath()
} }
async function exportData() { async function exportData() {
await files.export() await files.export()
}
async function importData() {
await files.import()
document.getElementById("silksong-path-input").value = await save.loadSilksongPath()
} }

View File

@@ -1,208 +1,208 @@
* { * {
margin: 0; margin: 0;
padding: 0; padding: 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;
} }
body { body {
background: black; background: black;
color: #eee; color: #eee;
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
} }
.app { .app {
display: flex; display: flex;
height: 100vh; height: 100vh;
} }
.background_video { .background_video {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
z-index: -1; z-index: -1;
filter: brightness(1.4); filter: brightness(1.4);
} }
.sidebar { .sidebar {
width: 280px; width: 280px;
background: rgba(0, 0, 0, 0.8); background: rgba(0, 0, 0, 0.8);
border-right: 1px solid rgba(255, 0, 0, 0.15); border-right: 1px solid rgba(255, 0, 0, 0.15);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.logo { .logo {
height: 70px; height: 70px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 32px; font-size: 32px;
cursor: pointer; cursor: pointer;
border-bottom: 1px solid rgba(255, 72, 0, 0.2); border-bottom: 1px solid rgba(255, 72, 0, 0.2);
transition: background 0.2s; transition: background 0.2s;
} }
.logo:hover { .logo:hover {
background: rgba(255, 0, 0, 0.08); background: rgba(255, 0, 0, 0.08);
} }
.logo_img { .logo_img {
height: 50px; height: 50px;
width: auto; width: auto;
} }
.nav { .nav {
padding: 20px; padding: 20px;
} }
.nav-section { .nav-section {
margin-bottom: 30px; margin-bottom: 30px;
} }
.nav-title { .nav-title {
display: inline-block; display: inline-block;
font-size: 13px; font-size: 13px;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 1px; letter-spacing: 1px;
color: #fff; color: #fff;
margin-bottom: 10px; margin-bottom: 10px;
padding: 0 4px 4px; padding: 0 4px 4px;
border-bottom: 1px solid #ff6b6b; border-bottom: 1px solid #ff6b6b;
} }
.nav button { .nav button {
width: 100%; width: 100%;
padding: 10px 14px; padding: 10px 14px;
margin-bottom: 8px; margin-bottom: 8px;
background: transparent; background: transparent;
border: 1px solid black; border: 1px solid black;
border-radius: 4px; border-radius: 4px;
color: #eee; color: #eee;
cursor: pointer; cursor: pointer;
text-align: left; text-align: left;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.nav button:hover { .nav button:hover {
background: rgba(255, 72, 0, 0.2); background: rgba(255, 72, 0, 0.2);
border-color: rgba(255, 25, 0, 0.3); border-color: rgba(255, 25, 0, 0.3);
} }
.content { .content {
flex: 1; flex: 1;
padding: 40px; padding: 40px;
} }
.content h1 { .content h1 {
font-size: 28px; font-size: 28px;
margin-bottom: 20px; margin-bottom: 20px;
color: #ffffff; color: #ffffff;
} }
.content h2 { .content h2 {
font-size: 24px; font-size: 24px;
margin-bottom: 20px; margin-bottom: 20px;
color: #ffffff; color: #ffffff;
padding: 0 4px 4px; padding: 0 4px 4px;
border-bottom: 1px solid #ff6b6b; border-bottom: 1px solid #ff6b6b;
} }
.view { .view {
width: 100%; width: 100%;
background: rgba(0, 0, 0, 0.8); background: rgba(0, 0, 0, 0.8);
border-radius: 12px; border-radius: 12px;
padding: 40px; padding: 40px;
position: relative; position: relative;
z-index: 0; z-index: 0;
box-shadow: 0 0 50px rgba(0, 0, 0, 0.15); box-shadow: 0 0 50px rgba(0, 0, 0, 0.15);
overflow: auto; overflow: auto;
height: 90%; height: 90%;
} }
.slider-container { .slider-container {
margin-top: 20px; margin-top: 20px;
width: clamp(600px, 70vw, 900px); width: clamp(600px, 70vw, 900px);
} }
input[type="range"] { input[type="range"] {
width: 100%; width: 100%;
} }
.horizontal-div { .horizontal-div {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 20px; gap: 20px;
} }
.silksong-path-input { .silksong-path-input {
flex: 1; flex: 1;
height: 30px; height: 30px;
padding: 0 12px; padding: 0 12px;
background: rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.4);
border: 1px solid #ff6b6b; border: 1px solid #ff6b6b;
border-radius: 4px; border-radius: 4px;
color: #eee; color: #eee;
font-size: 13px; font-size: 13px;
outline: none; outline: none;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.silksong-path-input:hover { .silksong-path-input:hover {
background: rgba(0, 0, 0, 0.55); background: rgba(0, 0, 0, 0.55);
border-color: rgba(255, 25, 0, 0.3); border-color: rgba(255, 25, 0, 0.3);
} }
.silksong-path-input:focus { .silksong-path-input:focus {
background: rgba(0, 0, 0, 0.65); background: rgba(0, 0, 0, 0.65);
border-color: rgba(255, 25, 0, 0.3); border-color: rgba(255, 25, 0, 0.3);
box-shadow: 0 0 0 1px rgba(255, 25, 0, 0.2); box-shadow: 0 0 0 1px rgba(255, 25, 0, 0.2);
} }
.default-button { .default-button {
width: 120px; width: 120px;
height: 40px; height: 40px;
padding: 2px; padding: 2px;
background: rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.4);
border: 1px solid #ff6b6b; border: 1px solid #ff6b6b;
border-radius: 4px; border-radius: 4px;
color: #eee; color: #eee;
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 16px;
transition: all 0.2s ease; transition: all 0.2s ease;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.default-button:hover { .default-button:hover {
background: rgba(255, 72, 0, 0.2); background: rgba(255, 72, 0, 0.2);
border-color: rgba(255, 25, 0, 0.3); border-color: rgba(255, 25, 0, 0.3);
} }
.important-button { .important-button {
width: 120px; width: 120px;
height: 40px; height: 40px;
padding: 2px; padding: 2px;
background: rgba(100, 0, 0, 0.4); background: rgba(100, 0, 0, 0.4);
border: 1px solid rgba(200, 25, 0); border: 1px solid rgba(200, 25, 0);
border-radius: 4px; border-radius: 4px;
color: #eee; color: #eee;
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 16px;
transition: all 0.2s ease; transition: all 0.2s ease;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.important-button:hover { .important-button:hover {
background: rgba(255, 0, 0, 0.4); background: rgba(255, 0, 0, 0.4);
border-color: rgba(255, 25, 0, 0.8); border-color: rgba(255, 25, 0, 0.8);
} }