Compare commits
9 Commits
v1.0.0-bet
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
| 17043cde37 | |||
| 41d84b18b7 | |||
| b5eef93ffd | |||
| 90eb204021 | |||
| eb923a0c57 | |||
| c93e930286 | |||
| 380b9a4604 | |||
| 0e895fe1fb | |||
| 0feb6fa073 |
90
README.MD
@@ -1,13 +1,10 @@
|
||||
<h1 align="center">Silk Fly Launcher</h1>
|
||||
<h3 align="center">
|
||||
— A Silksong mod manager —
|
||||
</h3>
|
||||
|
||||
<p align="center">
|
||||
<img alt="Release" src="https://img.shields.io/github/v/release/Gabi-Zar/Silk-Fly-Launcher">
|
||||
<img alt="Stars" src="https://img.shields.io/github/stars/Gabi-Zar/Silk-Fly-Launcher?color=magenta">
|
||||
<img alt="Forks" src="https://img.shields.io/github/forks/Gabi-Zar/Silk-Fly-Launcher?color=purple">
|
||||
<img alt="License" src="https://img.shields.io/github/license/Gabi-Zar/Silk-Fly-Launcher?color=BB0000">
|
||||
<img alt="Release" src="https://img.shields.io/github/v/release/Gabi-Zar/Silk-Fly-Launcher?include_prereleases&style=flat&color=green">
|
||||
<img alt="Stars" src="https://img.shields.io/github/stars/Gabi-Zar/Silk-Fly-Launcher?style=flat&color=magenta">
|
||||
<img alt="Forks" src="https://img.shields.io/github/forks/Gabi-Zar/Silk-Fly-Launcher?style=flat&color=purple">
|
||||
<img alt="License" src="https://img.shields.io/github/license/Gabi-Zar/Silk-Fly-Launcher?style=flat&color=BB0000">
|
||||
<br>
|
||||
<a href="https://github.com/Gabi-Zar"><img title="Developer" src="https://img.shields.io/badge/developer-GabiZar-blue"></a>
|
||||
<img alt="Maintained" src="https://img.shields.io/badge/Maintained-Yes-009900">
|
||||
@@ -16,17 +13,15 @@
|
||||
|
||||
---
|
||||
|
||||
Silk Fly Launcher is an open-source mod manager for Hollow Knight: Silksong, designed to simplify installing and managing Nexus Mods.
|
||||
|
||||
Built with Electron, it provides a clean UI, secure Nexus API integration, and seamless switching between vanilla and modded gameplay.
|
||||
Silk Fly Launcher is an open-source mod manager for Hollow Knight: Silksong built with Electron, it download mods from Nexus Mods (Thunderstore comming in stable v1.0.0).
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/github/about.png">
|
||||
</p>
|
||||
|
||||
<details>
|
||||
<summary>More screenshots (click me)</summary>
|
||||
<p align="center">
|
||||
<summary>More screenshots (click me)</summary>
|
||||
<p align="center">
|
||||
<img src="assets/github/online-mods.png">
|
||||
<img src="assets/github/settings.png">
|
||||
<img src="assets/github/about-steel.png">
|
||||
@@ -34,77 +29,78 @@ Built with Electron, it provides a clean UI, secure Nexus API integration, and s
|
||||
<img src="assets/github/installed-mods-cradle.png">
|
||||
<img src="assets/github/about-greyroot.png">
|
||||
<img src="assets/github/latest-mods-abyss.png">
|
||||
</p>
|
||||
</p>
|
||||
</details>
|
||||
|
||||
## 🌟 Features
|
||||
## Features
|
||||
|
||||
- Install / Uninstall / Backup BepInEx for Silksong.
|
||||
- Automatically detect Silksong installation path for Steam on Windows
|
||||
- Browse and download Nexus mods directly in the app
|
||||
- Browse and download Thunderstore and Nexus mods directly in the app
|
||||
- Activate and deactivate mods
|
||||
- Multiple themes inspired by Silksong
|
||||
- Launch Silksong in Vanilla or Modded without deleting the mods
|
||||
- Securely store your Nexus API key on your device using Electron's safeStorage, and then additionally encrypt it with an AES key (provided at build time) via Electron Store.
|
||||
- Securely store your Nexus API key on your device using Electron's safeStorage, and then encrypt it with an AES key (provided at build time) via Electron Store.
|
||||
- Works on Windows (Linux coming soon)
|
||||
|
||||
## 💻 Compatibility
|
||||
## Compatibility
|
||||
|
||||
- ✅ Windows x64
|
||||
- 🛠 Linux (Comming in v1.0.0)
|
||||
- ❌ macOS (need a tester)
|
||||
- [x] Windows x64
|
||||
- [ ] Linux (Comming in v1.0.0)
|
||||
- [ ] macOS (need a tester)
|
||||
|
||||
## 🚀 Installation & Usage
|
||||
## Installation & Usage
|
||||
|
||||
1. Download the app in your desired format from the [GitHub Releases](https://github.com/Gabi-Zar/Silk-Fly-Launcher/releases) page or build it from [source](#how-to-build) to have the latest features.
|
||||
1. Download the app in your desired format from the [GitHub Releases](https://github.com/Gabi-Zar/Silk-Fly-Launcher/releases) page or build it from source to have the latest features.
|
||||
|
||||
2. Install it and follow the in-app guide
|
||||
2. Install it and follow the in-app guide.
|
||||
|
||||
## ⚙️ How to build
|
||||
## How to build
|
||||
|
||||
1. Install [Git](https://git-scm.com/) and [Node JS](https://nodejs.org)
|
||||
2. Clone the repo `git clone https://github.com/Gabi-Zar/Silk-Fly-Launcher `
|
||||
3. Go into the repository `cd Silk-Fly-Launcher `
|
||||
4. Install npm dependencies `npm install `
|
||||
5. Start the app with `npm run start `
|
||||
6. Build the app with `npm run make:options `
|
||||
7. Available Build options are
|
||||
6. Optionnal dependencies:
|
||||
- [Wix toolset v3](https://github.com/wixtoolset/wix3/releases/tag/wix3141rtm) for msi windows build
|
||||
7. Build the app with `npm run make:options `
|
||||
8. Available Build options:
|
||||
- none - build all
|
||||
- zip - Make a zip for Windows x64
|
||||
- msi - Make a msi build for Windows x64
|
||||
|
||||
```
|
||||
none - build all
|
||||
zip - Make a zip for Windows x64
|
||||
msi - Make a msi build for Windows x64
|
||||
```
|
||||
## Todo
|
||||
|
||||
## ✅ Todo
|
||||
|
||||
#### For release 1.0.0
|
||||
#### For stable release 1.0.0
|
||||
|
||||
- [x] BepInEx support
|
||||
- [x] Nexus support
|
||||
- [ ] Disable / Enable Individual mods
|
||||
- [ ] Support for offline mods
|
||||
- [ ] Auto Download Mods Dependencies
|
||||
- [x] Disable / Enable Individual mods
|
||||
- [x] Automatic update
|
||||
- [x] Support for Thunderstore
|
||||
- [ ] Linux support
|
||||
- [ ] Automatic update
|
||||
- [x] Support for offline mods
|
||||
- [ ] Auto Download Mods Dependencies
|
||||
|
||||
#### For Later
|
||||
#### For later
|
||||
|
||||
- [ ] Automatically detect Silksong installation path on other platforms
|
||||
- [ ] Multiple mods profiles
|
||||
- [ ] Support for MelonLoader
|
||||
- [ ] Support for Thunderstore
|
||||
- [ ] French translation
|
||||
- [ ] macOS support (need a tester)
|
||||
|
||||
## 🤝 Contributing
|
||||
##### Todo list sorted by importance
|
||||
|
||||
Pull requests are welcome.
|
||||
For major changes, please open an issue first to discuss what you would like to change.
|
||||
## Contributing
|
||||
|
||||
## 📜 License and credit
|
||||
This is my first Electron project, I will love any feedback. Just open an issue or make a pull request.
|
||||
|
||||
This project is licensed under the [GPL-3.0 license](LICENSE).
|
||||
This product uses third-party modules or assets under open source [third-party](THIRD-PARTY-LICENSES) licenses
|
||||
## License and credit
|
||||
|
||||
This project is licensed under the [GPL-3.0 license](LICENSE).<br>
|
||||
This product uses third-party modules or assets under open source [third-party](THIRD-PARTY-LICENSES) licenses.
|
||||
|
||||
> [!CAUTION]
|
||||
> Some assets came from Hollow Knight: Silksong.
|
||||
@@ -113,4 +109,4 @@ This product uses third-party modules or assets under open source [third-party](
|
||||
|
||||
---
|
||||
|
||||
<p align="center">If you like this app, consider giving it a ⭐ on GitHub!</p>
|
||||
<p align="center">If you like this app, consider giving it a star on GitHub!</p>
|
||||
|
||||
@@ -8,6 +8,152 @@ Hollow Knight: Silksong is property of Team Cherry.
|
||||
This project is not affiliated with or endorsed by Team Cherry.
|
||||
|
||||
|
||||
LibreICONS
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Diemen Design
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
Iconbuddy Simple Icons
|
||||
CC0 1.0
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
|
||||
|
||||
7zip-bin 5.2.0
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
@@ -12,6 +12,7 @@ if (buildTarget == "msi" || buildTarget == "all") {
|
||||
config: {
|
||||
icon: "./assets/icon.ico",
|
||||
ui: { enabled: true, chooseDirectory: true },
|
||||
upgradeCode: "e3bb759f-c2b2-4545-bb7b-35bed5be4cd5",
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -23,15 +24,6 @@ if (buildTarget == "zip" || buildTarget == "all") {
|
||||
});
|
||||
}
|
||||
|
||||
async function fileExists(filePath) {
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
packagerConfig: {
|
||||
asar: true,
|
||||
|
||||
363
main.js
@@ -11,6 +11,9 @@ import { path7za } from "7zip-bin";
|
||||
import node7z from "node-7z";
|
||||
const { extractFull } = node7z;
|
||||
import packageJson from "./package.json" with { type: "json" };
|
||||
import semverGt from "semver/functions/gt.js";
|
||||
import { randomUUID } from "crypto";
|
||||
import { unzip } from "zlib";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -36,9 +39,11 @@ let installedCachedModList;
|
||||
let installedTotalModsCount;
|
||||
let onlineCachedModList;
|
||||
let onlineTotalModsCount;
|
||||
let thunderstoreCachedModList;
|
||||
let thunderstoreTotalModsCount;
|
||||
|
||||
const bepinexFiles = [".doorstop_version", "changelog.txt", "doorstop_config.ini", "winhttp.dll"];
|
||||
let bepinexVersion;
|
||||
let bepinexVersion = bepinexStore.get("bepinex-version");
|
||||
let bepinexBackupVersion;
|
||||
|
||||
let mainWindow;
|
||||
@@ -79,6 +84,9 @@ async function createWindow() {
|
||||
|
||||
mainWindow.once("ready-to-show", () => {
|
||||
mainWindow.show();
|
||||
if (!isDev) {
|
||||
verifyUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,13 +123,60 @@ app.on("open-url", (event, url) => {
|
||||
handleNxmUrl(url);
|
||||
});
|
||||
|
||||
async function verifyUpdate() {
|
||||
const GITHUB_URL = "https://api.github.com/repos/Gabi-Zar/Silk-Fly-Launcher/releases";
|
||||
|
||||
const res = await fetch(GITHUB_URL, {
|
||||
headers: {
|
||||
"User-Agent": userAgent,
|
||||
Accept: "application/vnd.github+json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
if (res.status == 403) {
|
||||
mainWindow.webContents.send("showToast", "Github has blocked the application. Please try again later.", "error");
|
||||
}
|
||||
throw new Error(`GitHub API error: ${res.status}`);
|
||||
}
|
||||
|
||||
const releases = await res.json();
|
||||
const prerelease = releases.find((r) => r.prerelease);
|
||||
const release = releases.find((r) => !r.prerelease && !r.draft);
|
||||
|
||||
let prereleaseVersion;
|
||||
let releaseVersion;
|
||||
let latestVersion;
|
||||
|
||||
if (prerelease) {
|
||||
prereleaseVersion = prerelease.tag_name.replace(/^v/, "");
|
||||
latestVersion = prereleaseVersion;
|
||||
}
|
||||
if (release) {
|
||||
releaseVersion = release.tag_name.replace(/^v/, "");
|
||||
latestVersion = releaseVersion;
|
||||
}
|
||||
|
||||
if (prereleaseVersion && releaseVersion) {
|
||||
latestVersion = semverGt(prereleaseVersion, releaseVersion) ? prereleaseVersion : releaseVersion;
|
||||
}
|
||||
if (latestVersion != VERSION) {
|
||||
mainWindow.webContents.send(
|
||||
"showBanner",
|
||||
`Update v${latestVersion} is available on <a href="" class="link" onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar/Silk-Fly-Launcher/releases/tag/v${latestVersion}')">GitHub</a>! Your current version is ${VERSION}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
///////////////// SAVING AND LOADING /////////////////
|
||||
ipcMain.handle("save-path", (event, path) => {
|
||||
saveSilksongPath(path);
|
||||
});
|
||||
|
||||
function saveSilksongPath(path) {
|
||||
store.set("silksong-path", path);
|
||||
checkInstalledMods();
|
||||
}
|
||||
|
||||
function loadSilksongPath() {
|
||||
@@ -201,13 +256,33 @@ ipcMain.handle("load-theme", () => {
|
||||
return theme;
|
||||
});
|
||||
|
||||
async function saveModInfo(modId, suppr = false) {
|
||||
async function saveModInfo(modId, suppr = false, optionalModInfo = {}) {
|
||||
if (suppr == true) {
|
||||
installedModsStore.delete(String(modId));
|
||||
return;
|
||||
}
|
||||
|
||||
const modInfo = onlineCachedModList.find((mod) => mod.modId == modId);
|
||||
let modInfo;
|
||||
if (onlineCachedModList) {
|
||||
modInfo = onlineCachedModList.find((mod) => mod.modId == modId);
|
||||
}
|
||||
if (!modInfo) {
|
||||
if (thunderstoreCachedModList) {
|
||||
modInfo = thunderstoreCachedModList.find((mod) => mod.modId == modId);
|
||||
}
|
||||
}
|
||||
if (!modInfo) {
|
||||
modInfo = optionalModInfo;
|
||||
}
|
||||
modInfo.activated = true;
|
||||
|
||||
const modFiles = await fs.readdir(path.join(modSavePath, modId));
|
||||
modInfo.fileSize = 0;
|
||||
for (const file of modFiles) {
|
||||
const fileStats = await fs.stat(path.join(modSavePath, modId, file));
|
||||
modInfo.fileSize += fileStats.size;
|
||||
}
|
||||
|
||||
installedModsStore.set(String(modId), modInfo);
|
||||
}
|
||||
|
||||
@@ -310,9 +385,7 @@ async function installBepinex() {
|
||||
saveBepinexVersion(release.tag_name);
|
||||
}
|
||||
|
||||
if (await fileExists(modSavePath)) {
|
||||
await fs.cp(modSavePath, path.join(silksongPath, "BepInEx", "plugins"), { recursive: true });
|
||||
}
|
||||
checkInstalledMods();
|
||||
}
|
||||
|
||||
ipcMain.handle("install-bepinex", async () => {
|
||||
@@ -401,7 +474,7 @@ ipcMain.handle("delete-bepinex-backup", async () => {
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
/////////////////////// NEXUS ////////////////////////
|
||||
//////////////// NEXUS / THUNDERSTORE ////////////////
|
||||
|
||||
async function createNexus(api) {
|
||||
if (api == undefined) {
|
||||
@@ -414,9 +487,10 @@ async function createNexus(api) {
|
||||
} catch (error) {
|
||||
if (error.mStatusCode == 401) {
|
||||
mainWindow.webContents.send("showToast", "Invalid Nexus API key", "error");
|
||||
}
|
||||
if (error.code == "ENOTFOUND") {
|
||||
} else if (error.code == "ENOTFOUND") {
|
||||
mainWindow.webContents.send("showToast", "Unable to communicate with Nexus servers", "error");
|
||||
} else {
|
||||
mainWindow.webContents.send("showToast", "Unable to create Nexus API ", "error");
|
||||
}
|
||||
nexus = undefined;
|
||||
}
|
||||
@@ -440,12 +514,17 @@ ipcMain.handle("get-mods", async (event, type) => {
|
||||
if (!installedCachedModList) {
|
||||
await searchInstalledMods("");
|
||||
}
|
||||
return { modsInfo: installedCachedModList, installedTotalCount: installedTotalModsCount };
|
||||
return { installedModsInfo: installedCachedModList, installedTotalCount: installedTotalModsCount };
|
||||
} else if (type == "mods-online") {
|
||||
if (!onlineCachedModList) {
|
||||
await searchNexusMods("");
|
||||
}
|
||||
return { mods: onlineCachedModList, onlineTotalCount: onlineTotalModsCount };
|
||||
return { onlineModsInfo: onlineCachedModList, onlineTotalCount: onlineTotalModsCount };
|
||||
} else if (type == "mods-thunderstore") {
|
||||
if (!thunderstoreCachedModList) {
|
||||
await searchThunderstoreMods("");
|
||||
}
|
||||
return { thunderstoreModsInfo: thunderstoreCachedModList, thunderstoreTotalCount: thunderstoreTotalModsCount };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -511,38 +590,6 @@ async function startDownload(modId, fileId, key, expires) {
|
||||
installedCachedModList = undefined;
|
||||
}
|
||||
|
||||
async function checkInstalledMods() {
|
||||
const bepinexFolderPath = path.join(loadSilksongPath(), "BepInEx");
|
||||
|
||||
for (const [key, modInfo] of Object.entries(installedModsStore.store)) {
|
||||
modInfo.modId = String(modInfo.modId);
|
||||
if (!(await fileExists(path.join(modSavePath, modInfo.modId)))) {
|
||||
saveModInfo(key, true);
|
||||
await fs.rm(path.join(bepinexFolderPath, "plugins", modInfo.modId), { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.handle("uninstall-mod", async (event, modId) => {
|
||||
modId = String(modId);
|
||||
const BepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
|
||||
const modPath = path.join(BepinexPluginsPath, modId);
|
||||
if (await fileExists(path.join(modSavePath, modId))) {
|
||||
await fs.rm(path.join(modSavePath, modId), { recursive: true });
|
||||
}
|
||||
if (await fileExists(modPath)) {
|
||||
await fs.rm(modPath, { recursive: true });
|
||||
}
|
||||
|
||||
for (let i = 0; i < installedCachedModList.length; i++) {
|
||||
if (installedCachedModList[i].modId == modId) {
|
||||
installedCachedModList.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
saveModInfo(modId, true);
|
||||
});
|
||||
|
||||
ipcMain.handle("search-nexus-mods", async (event, keywords, offset, count, sortFilter, sortOrder) => {
|
||||
await searchNexusMods(keywords, offset, count, sortFilter, sortOrder);
|
||||
});
|
||||
@@ -600,6 +647,7 @@ async function searchNexusMods(keywords, offset = 0, count = 10, sortFilter = "d
|
||||
onlineCachedModList = data.mods.nodes;
|
||||
|
||||
for (let i = 0; i < onlineCachedModList.length; i++) {
|
||||
onlineCachedModList[i].source = "nexusmods";
|
||||
if (onlineCachedModList[i].modId == 26) {
|
||||
onlineCachedModList.splice(i, 1);
|
||||
}
|
||||
@@ -608,6 +656,89 @@ async function searchNexusMods(keywords, offset = 0, count = 10, sortFilter = "d
|
||||
onlineTotalModsCount = data.mods.totalCount;
|
||||
}
|
||||
|
||||
ipcMain.handle("search-thunderstore-mods", async (event, keywords, offset, count, sortFilter, sortOrder) => {
|
||||
await searchThunderstoreMods(keywords, offset, count, sortFilter, sortOrder);
|
||||
});
|
||||
|
||||
async function searchThunderstoreMods(keywords, offset = 0, count = 10, sortFilter = "downloads", sortOrder = "DESC") {
|
||||
const res = await fetch("https://thunderstore.io/c/hollow-knight-silksong/api/v1/package/", {
|
||||
headers: {
|
||||
"User-Agent": userAgent,
|
||||
},
|
||||
});
|
||||
let modsInfo = await res.json();
|
||||
|
||||
const modsToRemove = [
|
||||
"f21c391c-0bc5-431d-a233-95323b95e01b",
|
||||
"58c9e43a-549d-4d49-a576-4eed36775a84",
|
||||
"0fc63a3b-3c69-4be5-85f8-bb127eec81b3",
|
||||
"d5419c5d-c22a-4a47-b73d-ba4101f28635",
|
||||
"d5b65a03-1217-4496-8af6-8dda4d763676",
|
||||
"42f76853-d2a4-4520-949b-13a02fdbbbcb",
|
||||
"34eac80c-5497-470e-b98c-f53421b828c0",
|
||||
];
|
||||
let reMappedModsInfo = [];
|
||||
for (let i = 0; i < modsInfo.length; i++) {
|
||||
modsInfo[i].source = "thunderstore";
|
||||
if (modsToRemove.includes(modsInfo[i].uuid4)) {
|
||||
modsInfo.splice(i, 1);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
reMappedModsInfo.push(reMapThunderstoreModsInfo(modsInfo[i]));
|
||||
}
|
||||
|
||||
const result = sortAndFilterModsList(reMappedModsInfo, keywords, offset, count, sortFilter, sortOrder);
|
||||
thunderstoreCachedModList = result.list;
|
||||
thunderstoreTotalModsCount = result.totalCount;
|
||||
}
|
||||
|
||||
function reMapThunderstoreModsInfo(modInfo) {
|
||||
let totalDownloads = 0;
|
||||
for (const version of modInfo.versions) {
|
||||
totalDownloads += version.downloads;
|
||||
}
|
||||
|
||||
return {
|
||||
author: modInfo.owner,
|
||||
endorsements: modInfo.rating_score,
|
||||
modId: modInfo.uuid4,
|
||||
name: modInfo.name,
|
||||
pictureUrl: modInfo.versions[0].icon,
|
||||
summary: modInfo.versions[0].description,
|
||||
updatedAt: modInfo.date_updated,
|
||||
createdAt: modInfo.date_created,
|
||||
version: modInfo.versions[0].version_number,
|
||||
downloads: totalDownloads,
|
||||
fileSize: modInfo.versions[0].file_size,
|
||||
source: modInfo.source,
|
||||
};
|
||||
}
|
||||
|
||||
ipcMain.handle("download-thunderstore-mods", async (event, url, modId) => {
|
||||
const bepinexFolderPath = path.join(loadSilksongPath(), "BepInEx");
|
||||
if (!(await fileExists(loadSilksongPath()))) {
|
||||
mainWindow.webContents.send("showToast", "Path to the game invalid", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await fileExists(modSavePath))) {
|
||||
await fs.mkdir(modSavePath);
|
||||
}
|
||||
|
||||
await downloadAndUnzip(url, path.join(modSavePath, modId));
|
||||
if (await fileExists(bepinexFolderPath)) {
|
||||
await fs.cp(path.join(modSavePath, modId), path.join(bepinexFolderPath, "plugins", modId), { recursive: true });
|
||||
}
|
||||
|
||||
saveModInfo(modId);
|
||||
mainWindow.webContents.send("showToast", "Mod downloaded successfully.");
|
||||
installedCachedModList = undefined;
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
//////////////////////// MODS ////////////////////////
|
||||
|
||||
ipcMain.handle("search-installed-mods", async (event, keywords, offset, count, sortFilter, sortOrder) => {
|
||||
await searchInstalledMods(keywords, offset, count, sortFilter, sortOrder);
|
||||
});
|
||||
@@ -618,23 +749,156 @@ async function searchInstalledMods(keywords, offset = 0, count = 10, sortFilter
|
||||
modsInfo.push(modInfo);
|
||||
}
|
||||
|
||||
const modsInfoFiltered = modsInfo.filter((mod) => mod.name.toLowerCase().includes(keywords.toLowerCase()));
|
||||
const result = sortAndFilterModsList(modsInfo, keywords, offset, count, sortFilter, sortOrder);
|
||||
installedCachedModList = result.list;
|
||||
installedTotalModsCount = result.totalCount;
|
||||
}
|
||||
|
||||
async function checkInstalledMods() {
|
||||
if (!loadSilksongPath()) {
|
||||
return;
|
||||
}
|
||||
const bepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
|
||||
|
||||
for (const [key, modInfo] of Object.entries(installedModsStore.store)) {
|
||||
modInfo.modId = String(modInfo.modId);
|
||||
if (!(await fileExists(path.join(modSavePath, modInfo.modId)))) {
|
||||
saveModInfo(key, true);
|
||||
if (await fileExists(path.join(bepinexPluginsPath, modInfo.modId))) {
|
||||
await fs.rm(path.join(bepinexPluginsPath, modInfo.modId), { recursive: true });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (modInfo.activated) {
|
||||
await fs.cp(path.join(modSavePath, modInfo.modId), path.join(bepinexPluginsPath, modInfo.modId), { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.handle("uninstall-mod", async (event, modId) => {
|
||||
modId = String(modId);
|
||||
const BepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
|
||||
const modPath = path.join(BepinexPluginsPath, modId);
|
||||
if (await fileExists(path.join(modSavePath, modId))) {
|
||||
await fs.rm(path.join(modSavePath, modId), { recursive: true });
|
||||
}
|
||||
if (await fileExists(modPath)) {
|
||||
await fs.rm(modPath, { recursive: true });
|
||||
}
|
||||
|
||||
for (let i = 0; i < installedCachedModList.length; i++) {
|
||||
if (installedCachedModList[i].modId == modId) {
|
||||
installedCachedModList.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
saveModInfo(modId, true);
|
||||
});
|
||||
|
||||
ipcMain.handle("activate-mod", async (event, modId) => {
|
||||
await activateMod(modId);
|
||||
});
|
||||
|
||||
async function activateMod(modId) {
|
||||
if (!loadSilksongPath()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const BepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
|
||||
|
||||
if (!installedModsStore.get(`${modId}.activated`)) {
|
||||
installedModsStore.set(`${modId}.activated`, true);
|
||||
}
|
||||
|
||||
if (bepinexVersion) {
|
||||
if (!(await fileExists(path.join(BepinexPluginsPath, String(modId))))) {
|
||||
await fs.cp(path.join(modSavePath, String(modId)), path.join(BepinexPluginsPath, String(modId)), { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.handle("deactivate-mod", async (event, modId) => {
|
||||
const BepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
|
||||
|
||||
if (installedModsStore.get(`${modId}.activated`)) {
|
||||
installedModsStore.set(`${modId}.activated`, false);
|
||||
|
||||
if (bepinexVersion) {
|
||||
if (await fileExists(path.join(BepinexPluginsPath, String(modId)))) {
|
||||
await fs.rm(path.join(BepinexPluginsPath, String(modId)), { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function sortAndFilterModsList(list, keywords, offset, count, sortFilter, sortOrder) {
|
||||
const listFiltered = list.filter((element) => element.name.toLowerCase().includes(keywords.toLowerCase()));
|
||||
const sortFactor = sortOrder == "ASC" ? 1 : -1;
|
||||
|
||||
let modsInfoSorted;
|
||||
let listSorted;
|
||||
if (sortFilter == "name" || sortFilter == "createdAt" || sortFilter == "updatedAt") {
|
||||
modsInfoSorted = modsInfoFiltered.sort((a, b) => sortFactor * a[sortFilter].localeCompare(b[sortFilter]));
|
||||
listSorted = listFiltered.sort((a, b) => sortFactor * a[sortFilter].localeCompare(b[sortFilter]));
|
||||
} else if (sortFilter == "downloads" || sortFilter == "endorsements" || sortFilter == "size") {
|
||||
if (sortFilter == "size") {
|
||||
sortFilter = "fileSize";
|
||||
}
|
||||
modsInfoSorted = modsInfoFiltered.sort((a, b) => sortFactor * (a[sortFilter] - b[sortFilter]));
|
||||
listSorted = listFiltered.sort((a, b) => sortFactor * (a[sortFilter] - b[sortFilter]));
|
||||
}
|
||||
|
||||
installedTotalModsCount = modsInfoSorted.length;
|
||||
installedCachedModList = modsInfoSorted.slice(offset, offset + count);
|
||||
return { list: listSorted.slice(offset, offset + count), totalCount: listSorted.length };
|
||||
}
|
||||
|
||||
ipcMain.handle("add-offline-mod", async () => {
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog({
|
||||
title: "Add mod",
|
||||
properties: ["openFile"],
|
||||
filters: [{ name: "mod", extensions: ["dll", "zip", "7z"] }],
|
||||
});
|
||||
|
||||
if (canceled || !filePaths) return false;
|
||||
|
||||
const fileName = path.basename(filePaths[0]);
|
||||
const extension = path.extname(fileName).toLowerCase();
|
||||
let uuid;
|
||||
do {
|
||||
uuid = randomUUID();
|
||||
} while (installedModsStore.get(uuid));
|
||||
|
||||
if (extension === ".dll") {
|
||||
await fs.mkdir(path.join(modSavePath, uuid));
|
||||
await fs.copyFile(filePaths[0], path.join(modSavePath, uuid, fileName));
|
||||
} else if ([".zip", ".7z"].includes(extension)) {
|
||||
await extractArchive(filePaths[0], path.join(modSavePath, uuid));
|
||||
} else {
|
||||
mainWindow.webContents.send("showToast", `Unsupported file type: ${extension}`, "error");
|
||||
return false;
|
||||
}
|
||||
|
||||
activateMod(uuid);
|
||||
|
||||
const time = new Date().toLocaleDateString();
|
||||
const splitedTime = time.split("/");
|
||||
let newTime = "";
|
||||
for (let i = 2; i >= 0; i--) {
|
||||
newTime = newTime.concat(splitedTime[i]);
|
||||
if (i > 0) {
|
||||
newTime = newTime.concat("-");
|
||||
}
|
||||
}
|
||||
|
||||
await saveModInfo(uuid, false, {
|
||||
modId: uuid,
|
||||
name: fileName.split(".").shift(),
|
||||
summary: "Local mod",
|
||||
updatedAt: newTime,
|
||||
createdAt: newTime,
|
||||
source: "local",
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
//////////////////// UNCATEGORIZE ////////////////////
|
||||
|
||||
@@ -682,6 +946,7 @@ ipcMain.handle("open-window", async (event, file) => {
|
||||
|
||||
ipcMain.handle("launch-game", async (event, mode) => {
|
||||
const silksongExecutablePath = path.join(loadSilksongPath(), "Hollow Knight Silksong.exe");
|
||||
const bepinexFolderPath = path.join(loadSilksongPath(), "BepInEx");
|
||||
if (!fileExists(silksongExecutablePath)) {
|
||||
mainWindow.webContents.send("showToast", "Path to the game invalid", "warning");
|
||||
return;
|
||||
|
||||
7
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "silkflylauncher",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.0-dev",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "silkflylauncher",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.0-dev",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@nexusmods/nexus-api": "^1.1.5",
|
||||
@@ -14,7 +14,8 @@
|
||||
"electron-store": "^11.0.2",
|
||||
"graphql": "^16.12.0",
|
||||
"graphql-request": "^7.4.0",
|
||||
"node-7z": "^3.0.0"
|
||||
"node-7z": "^3.0.0",
|
||||
"semver": "^7.7.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^7.11.1",
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
"electron-store": "^11.0.2",
|
||||
"graphql": "^16.12.0",
|
||||
"graphql-request": "^7.4.0",
|
||||
"node-7z": "^3.0.0"
|
||||
"node-7z": "^3.0.0",
|
||||
"semver": "^7.7.4"
|
||||
},
|
||||
"AES-key-nexus-api": "__AES_KEY__"
|
||||
}
|
||||
|
||||
22
preload.js
@@ -34,6 +34,11 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
||||
callback(message, type, duration);
|
||||
});
|
||||
},
|
||||
onShowBanner: (callback) => {
|
||||
ipcRenderer.on("showBanner", (event, message) => {
|
||||
callback(message);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld("bepinex", {
|
||||
@@ -45,9 +50,20 @@ contextBridge.exposeInMainWorld("bepinex", {
|
||||
|
||||
contextBridge.exposeInMainWorld("nexus", {
|
||||
verifyAPI: () => ipcRenderer.invoke("verify-nexus-api"),
|
||||
getMods: (type) => ipcRenderer.invoke("get-mods", type),
|
||||
download: (link) => ipcRenderer.invoke("open-download", link),
|
||||
uninstall: (modId) => ipcRenderer.invoke("uninstall-mod", modId),
|
||||
search: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-nexus-mods", keywords, offset, count, sortFilter, sortOrder),
|
||||
searchInstalled: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-installed-mods", keywords, offset, count, sortFilter, sortOrder),
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld("mods", {
|
||||
searchInstalled: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-installed-mods", keywords, offset, count, sortFilter, sortOrder),
|
||||
uninstall: (modId) => ipcRenderer.invoke("uninstall-mod", modId),
|
||||
getMods: (type) => ipcRenderer.invoke("get-mods", type),
|
||||
activateMods: (modId) => ipcRenderer.invoke("activate-mod", modId),
|
||||
deactivateMods: (modId) => ipcRenderer.invoke("deactivate-mod", modId),
|
||||
add: () => ipcRenderer.invoke("add-offline-mod"),
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld("thunderstore", {
|
||||
search: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-thunderstore-mods", keywords, offset, count, sortFilter, sortOrder),
|
||||
download: (url, modId) => ipcRenderer.invoke("download-thunderstore-mods", url, modId),
|
||||
});
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg role="img" focusable="false" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14"><path d="m 1,8.3051015 c 0,-0.6641 0.2078922,-1.253128 0.6236766,-1.772859 0.4157844,-0.51973 0.9412897,-0.854668 1.5822907,-0.999037 0.1790183,-0.791146 0.5890279,-1.437922 1.2184793,-1.946102 0.6294514,-0.508181 1.3570741,-0.762272 2.1713184,-0.762272 0.79692,0 1.507219,0.248316 2.130895,0.739172 0.623677,0.490857 1.027912,1.126083 1.212705,1.899904 l 0.190568,0 c 0.51973,0 0.999037,0.127045 1.437921,0.375361 0.438883,0.248316 0.791145,0.594803 1.045236,1.033686 C 12.86718,7.3118385 13,7.7853705 13,8.3051015 c 0,0.508181 -0.12127,0.981713 -0.363811,1.414822 -0.242541,0.4331085 -0.577479,0.7795955 -0.999038,1.0394605 -0.421559,0.259866 -0.889317,0.398461 -1.391723,0.415785 l -6.4735319,0 C 2.9980751,11.14052 2.3455245,10.846006 1.8084697,10.291627 1.2714148,9.7430225 1,9.0789225 1,8.3051015 Z m 0.987488,0 c 0,0.502406 0.1732435,0.935515 0.5197305,1.305101 0.346487,0.369586 0.7680462,0.5659275 1.2646776,0.5948015 l 6.4619829,0 c 0.496631,-0.0231 0.91819,-0.2252155 1.264677,-0.5948015 0.352262,-0.369586 0.525506,-0.80847 0.525506,-1.305101 0,-0.508181 -0.190568,-0.94129 -0.565929,-1.310876 -0.375361,-0.369586 -0.820019,-0.554379 -1.33975,-0.554379 l -0.923965,0 c -0.06352,0 -0.09817,-0.03465 -0.09817,-0.103947 L 9.055828,6.0067375 C 8.992308,5.3830605 8.72089,4.8575555 8.247358,4.4359965 7.773826,4.0144375 7.225222,3.8007705 6.59577,3.8007705 c -0.6294512,0 -1.1838304,0.213667 -1.6458131,0.635226 -0.4677575,0.421559 -0.7333975,0.947064 -0.7911454,1.570741 l -0.046198,0.329162 c 0,0.0693 -0.040424,0.103947 -0.1154957,0.103947 l -0.3060635,0 c -0.4850818,0.05775 -0.8893167,0.26564 -1.2127045,0.617901 -0.3233879,0.352262 -0.4908566,0.768047 -0.4908566,1.247354 z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1 +1 @@
|
||||
<svg role="img" focusable="false" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14"><path d="m 13,5.384615 v 5.07693 q 0,0.66346 -0.47596,1.13942 -0.47596,0.47596 -1.13942,0.47596 H 2.61538 q -0.663457,0 -1.139418,-0.47596 Q 1,11.125005 1,10.461545 V 3.538465 Q 1,2.875005 1.475962,2.399045 1.951923,1.923075 2.61538,1.923075 h 2.3077 q 0.66346,0 1.13942,0.47597 0.47596,0.47596 0.47596,1.13942 v 0.23077 h 4.84616 q 0.66346,0 1.13942,0.47596 Q 13,4.721155 13,5.384615 z"/></svg>
|
||||
<svg role="img" focusable="false" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="#ffffff"><path d="m 13,5.384615 v 5.07693 q 0,0.66346 -0.47596,1.13942 -0.47596,0.47596 -1.13942,0.47596 H 2.61538 q -0.663457,0 -1.139418,-0.47596 Q 1,11.125005 1,10.461545 V 3.538465 Q 1,2.875005 1.475962,2.399045 1.951923,1.923075 2.61538,1.923075 h 2.3077 q 0.66346,0 1.13942,0.47597 0.47596,0.47596 0.47596,1.13942 v 0.23077 h 4.84616 q 0.66346,0 1.13942,0.47596 Q 13,4.721155 13,5.384615 z"/></svg>
|
||||
|
Before Width: | Height: | Size: 503 B After Width: | Height: | Size: 518 B |
@@ -1 +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>
|
||||
<svg class="social-github" role="img" focusable="false" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="#ffffff"><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>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
1
renderer/assets/icons/nexus-mods.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24" fill="#ffffff"><path fill="#ffffff" d="M17.376 0c-.993 0-2.18.686-2.907 1.182c-1.676-.36-4.036-.545-6.787.635c-1.365-.513-2.425-.562-3.32-.488a2.16 2.16 0 0 0-1.27.429c-.33.22-2.788 2.69-3.069 4.652C-.15 7.508.68 8.932 1.218 9.718c-.44 1.76-.2 4.572.517 6.188c-.353 1.041-.713 2.089-.664 3.205c.01.584.061 1.188.398 1.684C1.72 21.19 4.528 24 6.545 24c.957 0 1.93-.428 3.07-1.24c2.16.383 4.402.348 6.448-.532c2.573 1.001 4.224.625 4.84.162c.587-.457 2.826-2.915 3.07-4.622c.1-.672-.023-1.638-1.226-3.397a11 11 0 0 0-.501-6.455c.396-1.069.673-2.188.59-3.337c-.015-.68-.221-1.167-.487-1.507c-.209-.335-2.415-2.39-4.028-2.91A3.1 3.1 0 0 0 17.376 0m-.03 2.082c.65.015 2.155 1.093 3.01 1.906l.355.34c-.959-.163-2.125.428-3.26 1.55a10.3 10.3 0 0 0-1.358 1.595c-.28.384-.517.768-.753 1.285l1.18.635l-3.895 1.477l-1.122-4.18l1.033.547c1.358-3.102 2.524-3.973 3.232-4.416h.015a5.1 5.1 0 0 1 1.49-.724zM12 3.065a9 9 0 0 1 2.22.279a8 8 0 0 0-.42.488a8.4 8.4 0 0 0-1.8-.196a8.34 8.34 0 0 0-5.897 2.432a8 8 0 0 1-.37-.433A8.9 8.9 0 0 1 12 3.065m-7.076.305c.71-.002 1.309.127 2.2.466a9.5 9.5 0 0 0-1.713 1.337c-.327-.542-.624-1.156-.488-1.803m-.606.042c-.162.96.428 2.126 1.55 3.264c.457.487 1.003.945 1.594 1.358c.383.281.767.517 1.283.754l.62-1.182l1.49 3.914l-4.176 1.122l.546-1.033c-3.099-1.36-3.969-2.526-4.412-3.235v-.015a5.1 5.1 0 0 1-.723-1.491l-.015-.074c.015-.65 1.092-2.156 1.904-3.013Zm16.035 1.483a1.3 1.3 0 0 1 .26.015l.14.023a5 5 0 0 1-.13 1.137v.015q-.152.574-.377 1.148a9.5 9.5 0 0 0-1.346-1.776c.547-.357 1.051-.546 1.453-.562M18.43 5.8a8.9 8.9 0 0 1 2.506 6.2a9 9 0 0 1-.27 2.183a8 8 0 0 0-.488-.425A8.4 8.4 0 0 0 20.364 12A8.33 8.33 0 0 0 18 6.173a8 8 0 0 1 .429-.373M3.315 9.905q.235.222.488.425A8.4 8.4 0 0 0 3.636 12c0 2.248.887 4.286 2.327 5.788a8 8 0 0 1-.426.376A8.9 8.9 0 0 1 3.065 12a9 9 0 0 1 .25-2.095m13.988 1.541l-.546 1.034c3.098 1.359 3.969 2.526 4.412 3.235v.014c.34.488.575.99.723 1.492l.014.074c-.014.65-1.092 2.156-1.903 3.013l-.34.354c.163-.96-.427-2.127-1.549-3.264a10.3 10.3 0 0 0-1.594-1.359a7 7 0 0 0-1.283-.753l-.605 1.152l-1.505-3.87zm-6.006 1.684l1.121 4.18l-1.033-.547c-1.357 3.102-2.523 3.973-3.231 4.416h-.015c-.487.34-.989.576-1.49.724l-.074.015c-.65-.015-2.154-1.093-3.01-1.906l-.354-.34c.959.163 2.124-.428 3.26-1.55c.488-.458.945-1.004 1.358-1.595c.28-.384.517-.768.753-1.285l-1.166-.635ZM3.72 16.663A9.5 9.5 0 0 0 5.086 18.5c-.697.47-1.33.665-1.777.59l-.138-.024c0-.367.038-.748.128-1.137v-.015c.11-.417.254-.835.42-1.252m14.131 1.314q.194.21.372.43A8.9 8.9 0 0 1 12 20.936a9 9 0 0 1-2.282-.296a8 8 0 0 0 .417-.487a8.34 8.34 0 0 0 7.716-2.175m.696.889c.43.666.607 1.267.534 1.698l-.023.138a5 5 0 0 1-1.136-.128h-.014a11 11 0 0 1-1.114-.366a9.5 9.5 0 0 0 1.753-1.342"/></svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
26
renderer/assets/icons/plus.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="244"
|
||||
height="244"
|
||||
viewBox="0 0 64.558333 64.558333"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-1.5875,-1.5875005)">
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:6.35;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 62.970833,33.866667 H 4.7625"
|
||||
id="path1" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:6.35;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 33.866666,62.970833 V 4.7625005"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 838 B |
@@ -3,7 +3,8 @@
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#ffffff">
|
||||
<path
|
||||
d="m 8.8752116,12.181305 v -0.845421 c 0.3824271,-0.154001 0.726847,-0.36571 1.0522956,-0.605995 l 0.7732288,0.43071 c 0.129524,0.07348 0.274423,0.111383 0.423312,0.111383 0.07294,0 0.14588,-0.0105 0.216596,-0.02797 0.216594,-0.05726 0.40349,-0.194791 0.514241,-0.381738 L 12.886967,9.137646 C 13.120376,8.7467822 12.980643,8.2446393 12.57519,8.0191521 L 11.798888,7.5845715 c 0.02336,-0.1937454 0.05915,-0.3841963 0.05915,-0.58406 0,-0.200282 -0.03578,-0.3919096 -0.05915,-0.5854981 L 12.57519,5.9817665 C 12.769673,5.8719503 12.910975,5.6936322 12.97129,5.4831529 13.02958,5.2718895 12.99661,5.0497752 12.88697,4.8614422 L 11.854885,3.1377619 C 11.699911,2.8751729 11.41463,2.7284127 11.122019,2.7284127 c -0.144899,0 -0.288488,0.034775 -0.421219,0.1098161 L 9.9275724,3.2698799 C 9.6021235,3.0301169 9.256657,2.8177291 8.8752766,2.6630465 V 1.8201103 C 8.8752766,1.3669134 8.4961856,1 8.0281931,1 H 5.9710286 C 5.5019897,1 5.1238146,1.3669134 5.1238146,1.8201103 V 2.6630465 C 4.7434808,2.8178336 4.3959207,3.0302213 4.0729577,3.2698799 L 3.2988145,2.8372867 C 3.1697472,2.7645994 3.0251103,2.7282557 2.8783147,2.7282557 c -0.073855,0 -0.1488238,0.010461 -0.2218277,0.027978 C 2.4398921,2.8119249 2.256332,2.9484095 2.1422449,3.1364026 L 1.1123216,4.8612069 c -0.23151155,0.3921709 -0.09407,0.8935296 0.3104686,1.1203242 l 0.7773489,0.433247 c -0.023163,0.193484 -0.059268,0.3851637 -0.059268,0.5855242 0,0.1997591 0.036177,0.39021 0.059268,0.583354 L 1.4227902,8.0174787 C 1.0182522,8.2431227 0.88081119,8.7454488 1.1123216,9.1373584 L 2.1421142,10.86198 c 0.1140877,0.186948 0.2977122,0.32453 0.5142421,0.381738 0.071957,0.0183 0.1458801,0.02797 0.218622,0.02797 0.149086,0 0.2937219,-0.03791 0.4237052,-0.111384 l 0.7722463,-0.430711 c 0.3249255,0.240286 0.6724201,0.452176 1.0528194,0.606022 v 0.84542 C 5.1237492,12.632693 5.5019241,13 5.9709632,13 h 2.0571645 c 0.4679925,1.83e-4 0.8470835,-0.367176 0.8470835,-0.818855 z M 4.663215,7.0004593 c 0,-1.2491741 1.0468009,-2.2607289 2.334205,-2.2607289 1.2928991,0 2.3395692,1.0114764 2.3395692,2.2607289 0,1.2470298 -1.0466701,2.2585586 -2.3395692,2.2585586 C 5.7100159,9.2589655 4.663215,8.2474891 4.663215,7.0004593 Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -3,7 +3,8 @@
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#ffffff">
|
||||
<path
|
||||
d="M 5.75,4.5454543 C 5.75,4.6562497 5.71289,4.7521362 5.6386718,4.8331133 5.5644534,4.9140684 5.4765624,4.9545458 5.375,4.9545458 H 4.625 V 11.5 h -1.5 V 4.9545458 h -0.75 c -0.1015625,0 -0.1894532,-0.040477 -0.2636719,-0.1214325 C 2.0371098,4.7521364 2,4.6562497 2,4.5454543 2,4.434659 2.03711,4.3387842 2.1113281,4.2578297 l 1.5,-1.6363638 C 3.6855468,2.5404886 3.7734374,2.5 3.8749999,2.5 c 0.1015625,0 0.1894532,0.04049 0.2636719,0.1214659 l 1.5,1.6363638 C 5.7128911,4.3387838 5.75,4.434659 5.75,4.5454543 Z" />
|
||||
<path
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -3,7 +3,8 @@
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#ffffff">
|
||||
<path
|
||||
d="M 6,3.7272726 C 6,3.8749998 5.9505205,4.0028484 5.8515624,4.1108179 5.7526046,4.2187579 5.6354166,4.2727279 5.5,4.2727279 H 4.4999999 V 13 H 2.5 V 4.2727279 h -1 c -0.1354167,0 -0.2526042,-0.053969 -0.3515625,-0.16191 C 1.0494797,4.0028487 1,3.8749998 1,3.7272726 1,3.5795455 1.0494795,3.4517124 1.1484375,3.3437731 l 2,-2.1818186 C 3.2473957,1.0539848 3.3645832,1 3.4999998,1 3.6354166,1 3.7526041,1.053986 3.8515624,1.1619545 l 2,2.1818186 C 5.9505213,3.4517119 6,3.5795455 6,3.7272726 Z" />
|
||||
<path
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -3,7 +3,8 @@
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#ffffff">
|
||||
<path
|
||||
d="m 10.781355,12.15887 v -0.352258 c 0.159345,-0.06416 0.302854,-0.152379 0.438458,-0.252498 l 0.32218,0.179463 c 0.05396,0.03062 0.114338,0.0464 0.176383,0.0464 0.03039,0 0.06079,-0.0044 0.09023,-0.01163 0.09026,-0.02386 0.168121,-0.08116 0.214269,-0.159057 l 0.430036,-0.718594 c 0.09725,-0.16286 0.03902,-0.372086 -0.129909,-0.466039 l -0.323454,-0.181075 c 0.0098,-0.08074 0.02464,-0.160081 0.02464,-0.243358 0,-0.08345 -0.01491,-0.1632952 -0.02464,-0.2439564 L 12.323,9.5757277 c 0.08104,-0.04575 0.139914,-0.1200624 0.165044,-0.2077546 0.02429,-0.08803 0.0105,-0.180574 -0.03513,-0.2590459 L 12.022891,8.3907278 c -0.06457,-0.1094124 -0.18344,-0.1705615 -0.305364,-0.1705615 -0.06038,0 -0.1202,0.01449 -0.175508,0.045756 L 11.21984,8.4457815 C 11.084235,8.3458802 10.940291,8.257386 10.781381,8.192935 V 7.8417121 C 10.781381,7.6528803 10.623426,7.5 10.428427,7.5 H 9.5712702 C 9.375837,7.5 9.2182616,7.6528803 9.2182616,7.8417121 V 8.192935 C 9.0597901,8.2574296 8.9149709,8.3459242 8.7804031,8.4457815 L 8.4578409,8.265535 C 8.4040669,8.235247 8.3438034,8.220105 8.2826318,8.220105 c -0.030776,0 -0.062013,0.00436 -0.092425,0.011662 -0.090237,0.023205 -0.166733,0.080074 -0.2142681,0.1584039 L 7.5468008,9.1088332 c -0.096463,0.1634049 -0.039188,0.3723033 0.1293616,0.466802 l 0.3238982,0.1805187 c -0.00962,0.080625 -0.024687,0.1604849 -0.024687,0.2439671 0,0.08324 0.015076,0.162588 0.024687,0.243065 l -0.3238977,0.180759 c -0.168558,0.09401 -0.2258256,0.30332 -0.1293616,0.466615 l 0.4290822,0.718592 c 0.047526,0.0779 0.1240501,0.13522 0.2142681,0.159057 0.029976,0.0076 0.060788,0.01162 0.091088,0.01162 0.062124,0 0.1223876,-0.0158 0.1765454,-0.04641 l 0.3217708,-0.179464 c 0.1353866,0.100125 0.280177,0.188406 0.4386774,0.25251 v 0.352258 c 0,0.18823 0.1575741,0.341274 0.3530085,0.341274 h 0.8571563 c 0.194997,7.6e-5 0.352954,-0.15299 0.352954,-0.341189 z m -1.75501,-2.158683 c 0,-0.520488 0.4361698,-0.9419688 0.9725912,-0.9419688 0.5387118,0 0.9748248,0.4214483 0.9748248,0.9419688 0,0.519595 -0.436113,0.941065 -0.9748248,0.941065 C 9.4625148,10.94123 9.026345,10.519782 9.026345,10.000187 Z" />
|
||||
<path
|
||||
|
||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -3,7 +3,8 @@
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#ffffff">
|
||||
<path
|
||||
d="m 2.1464844,1.0019531 c -0.1349019,-0.006832 -0.2388263,0.00771 -0.3085938,0.044922 -1.11627905,0.5953992 -1.11627905,11.3108509 0,11.9062499 C 2.9541697,13.548524 13,8.1907983 13,7 13,5.8836266 4.1700127,1.1044327 2.1464844,1.0019531 Z m 0.7617187,1.25 C 4.5101631,2.3330828 11.5,6.1162044 11.5,7 c 0,0.9427153 -7.9541697,5.184248 -8.8378906,4.712891 -0.883721,-0.471358 -0.883721,-8.954424 0,-9.4257816 0.055233,-0.02946 0.1392964,-0.040565 0.2460937,-0.035156 z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 616 B After Width: | Height: | Size: 634 B |
1
renderer/assets/icons/thunderstore.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 24 24" fill="#ffffff"><path fill="#ffffff" d="m.322 13.174l4.706 8.192L7.2 16.855L4.824 12.72a1.416 1.416 0 0 1 0-1.444l2.965-5.16c.265-.46.718-.723 1.245-.723h1.595l-3.086 6.953h3.812L6.171 22.403L16.583 9.914h-3.201l2.184-4.52h6.052L24 1.25H7.175c-.86 0-1.598.428-2.028 1.174l-4.825 8.4a2.306 2.306 0 0 0 0 2.35m7.213 9.576h9.29a2.29 2.29 0 0 0 2.03-1.176l4.825-8.4a2.317 2.317 0 0 0 0-2.35l-1.93-3.36h-4.763l2.19 3.813c.262.46.262.987 0 1.444l-2.964 5.162a1.41 1.41 0 0 1-1.248.723h-2.154l-1.497-.017z"/></svg>
|
||||
|
After Width: | Height: | Size: 591 B |
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Silk Fly Launcher</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self' https://*.nexusmods.com" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self' https://*.nexusmods.com https://*.thunderstore.io" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
@@ -17,15 +17,16 @@
|
||||
<h5 class="logo-title">Silk Fly Launcher</h5>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-content">
|
||||
<nav class="nav">
|
||||
<div class="nav-section">
|
||||
<span class="nav-title">Execute Silksong</span>
|
||||
<button class="horizontal-div" onclick="launch('vanilla')">
|
||||
<img src="assets/icons/start-vanilla.svg" class="icons invert-color" />
|
||||
<img src="assets/icons/start-vanilla.svg" class="icons" />
|
||||
Run Vanilla
|
||||
</button>
|
||||
<button class="horizontal-div" onclick="launch('modded')">
|
||||
<img src="assets/icons/start-modded.svg" class="icons invert-color" />
|
||||
<img src="assets/icons/start-modded.svg" class="icons" />
|
||||
Run Modded
|
||||
</button>
|
||||
</div>
|
||||
@@ -33,27 +34,35 @@
|
||||
<div class="nav-section">
|
||||
<span class="nav-title">Mods</span>
|
||||
<button class="horizontal-div" onclick="navigate('mods-installed')">
|
||||
<img src="assets/icons/folder.svg" class="icons invert-color" />
|
||||
<img src="assets/icons/folder.svg" class="icons" />
|
||||
Installed
|
||||
</button>
|
||||
<button class="horizontal-div" onclick="navigate('mods-online')">
|
||||
<img src="assets/icons/cloud.svg" class="icons invert-color" />
|
||||
Online
|
||||
<button class="horizontal-div" onclick="navigate('mods-thunderstore')">
|
||||
<img src="assets/icons/thunderstore.svg" class="icons" />
|
||||
Thunderstore
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="nav-section">
|
||||
<span class="nav-title">Settings</span>
|
||||
<button class="horizontal-div" onclick="navigate('general-settings')">
|
||||
<img src="assets/icons/settings.svg" class="icons invert-color" />
|
||||
General
|
||||
<button class="horizontal-div" onclick="navigate('mods-online')">
|
||||
<img src="assets/icons/nexus-mods.svg" class="icons" />
|
||||
Nexus
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<nav class="nav">
|
||||
<button class="horizontal-div" onclick="navigate('general-settings')">
|
||||
<img src="assets/icons/settings.svg" class="icons" />
|
||||
Settings
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main content -->
|
||||
<main class="content">
|
||||
<div class="banner-div" id="banner-div">
|
||||
<p id="banner-text"></p>
|
||||
<button class="default-button square-button" onclick="hideBanner()">X</button>
|
||||
</div>
|
||||
<h1 id="title">Silk Fly Launcher</h1>
|
||||
<div class="view" id="view"></div>
|
||||
<div class="toast-div" id="toast-div"></div>
|
||||
@@ -72,12 +81,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="horizontal-div">
|
||||
<img onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar/Silk-Fly-Launcher')" src="assets/icons/github.svg" alt="Github logo" class="logo-img invert-color" />
|
||||
<img onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar/Silk-Fly-Launcher')" src="assets/icons/github.svg" alt="Github logo" class="logo-img" />
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<ul>
|
||||
<li>Silk Fly Launcher is a launcher and mod manager for Silksong mods from Nexus, built with Electron.</li>
|
||||
<li>Silk Fly Launcher is a launcher and mod manager for Silksong mods from Nexus and Thunderstore, 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('THIRD-PARTY-LICENSES')">third-party licenses</a>.</li>
|
||||
<li>
|
||||
@@ -107,7 +116,8 @@
|
||||
<li id="size" onclick="changeSort('size')">by size</li>
|
||||
</div>
|
||||
</div>
|
||||
<button class="default-button square-button" onclick="inverseSort()"><img class="icons invert-color" id="sort-order-image" src="assets/icons/sort-order-1.svg" /></button>
|
||||
<button class="default-button square-button" onclick="inverseSort()"><img class="icons" id="sort-order-image" src="assets/icons/sort-order-1.svg" /></button>
|
||||
<button class="default-button square-button" onclick="addOfflineMod()"><img class="icons" src="assets/icons/plus.svg" /></button>
|
||||
</div>
|
||||
<div class="mods-container" id="mods-container"></div>
|
||||
<div class="separated-div">
|
||||
@@ -122,7 +132,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="online-mods-template">
|
||||
<template id="nexus-mods-template">
|
||||
<h2>List Of Nexus Mods</h2>
|
||||
<div class="horizontal-div">
|
||||
<form class="horizontal-div input-form" id="search-form">
|
||||
@@ -140,7 +150,40 @@
|
||||
<li id="size" onclick="changeSort('size')">by size</li>
|
||||
</div>
|
||||
</div>
|
||||
<button class="default-button square-button" onclick="inverseSort()"><img class="icons invert-color" id="sort-order-image" src="assets/icons/sort-order-1.svg" /></button>
|
||||
<button class="default-button square-button" onclick="inverseSort()"><img class="icons" id="sort-order-image" src="assets/icons/sort-order-1.svg" /></button>
|
||||
</div>
|
||||
<div class="mods-container" id="mods-container"></div>
|
||||
<div class="separated-div">
|
||||
<div class="horizontal-div">
|
||||
<button class="default-button" onclick="changeModsPage('min')">First page</button>
|
||||
<button class="default-button" onclick="changeModsPage(-1)">Previous</button>
|
||||
</div>
|
||||
<div class="horizontal-div">
|
||||
<button class="default-button" onclick="changeModsPage(1)">Next</button>
|
||||
<button class="default-button" onclick="changeModsPage('max')">Last page</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="thunderstore-mods-template">
|
||||
<h2>List Of Thunderstore Mods</h2>
|
||||
<div class="horizontal-div">
|
||||
<form class="horizontal-div input-form" id="search-form">
|
||||
<input class="input" id="search-input" type="text" placeholder="Search For Mods..." />
|
||||
<button class="default-button" onclick="searchThunderstoreMods()">Search</button>
|
||||
</form>
|
||||
<div class="list-div">
|
||||
<div class="default-button smaller-button" id="sort-button" onclick="toggleSortMenu()">Sort</div>
|
||||
<div class="list-menu longer-button list-menu-inverted" id="sort-menu">
|
||||
<li id="name" onclick="changeSort('name')">by name</li>
|
||||
<li id="downloads" onclick="changeSort('downloads')">by downloads count</li>
|
||||
<li id="endorsements" onclick="changeSort('endorsements')">by rating count</li>
|
||||
<li id="createdAt" onclick="changeSort('createdAt')">by date of creation</li>
|
||||
<li id="updatedAt" onclick="changeSort('updatedAt')">by date of updating</li>
|
||||
<li id="size" onclick="changeSort('size')">by size</li>
|
||||
</div>
|
||||
</div>
|
||||
<button class="default-button square-button" onclick="inverseSort()"><img class="icons" id="sort-order-image" src="assets/icons/sort-order-1.svg" /></button>
|
||||
</div>
|
||||
<div class="mods-container" id="mods-container"></div>
|
||||
<div class="separated-div">
|
||||
@@ -161,15 +204,21 @@
|
||||
<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>
|
||||
<p id="mod-downloads-number">? download</p>
|
||||
<p id="mod-endorsements-number"></p>
|
||||
<p id="mod-downloads-number"></p>
|
||||
<p id="mod-source"></p>
|
||||
</div>
|
||||
<p id="mod-description" class="long-text">No description provided</p>
|
||||
<p class="transparent-text" id="mod-version">V1.0.0 last update on 01/01/2026</p>
|
||||
<p class="transparent-text" id="mod-version"></p>
|
||||
<br />
|
||||
<div class="horizontal-div">
|
||||
<a href="www.nexusmods.com/hollowknightsilksong/mods" class="default-button" id="uninstall-mod-button">Uninstall</a>
|
||||
<a href="www.nexusmods.com/hollowknightsilksong/mods" class="default-button" id="external-link">Website</a>
|
||||
<a href="" class="default-button" id="uninstall-mod-button">Uninstall</a>
|
||||
<a href="" class="default-button" id="external-link">Website</a>
|
||||
<p>Activated:</p>
|
||||
<label class="checkbox-container">
|
||||
<input type="checkbox" name="activated-mod" id="activated-mod" />
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -221,7 +270,7 @@
|
||||
<li id="Steel" onclick="changeTheme('Steel')">Steel</li>
|
||||
</div>
|
||||
</div>
|
||||
<label class="lace-pin-checkbox-container">
|
||||
<label class="checkbox-container">
|
||||
<input type="checkbox" name="lace-pin" id="lace-pin" />
|
||||
<span class="checkmark"></span>
|
||||
Lace Pin
|
||||
|
||||
@@ -4,7 +4,8 @@ const view = document.getElementById("view");
|
||||
|
||||
const HomeTemplate = document.getElementById("home-template");
|
||||
const installedModsTemplate = document.getElementById("installed-mods-template");
|
||||
const onlineModsTemplate = document.getElementById("online-mods-template");
|
||||
const nexusModsTemplate = document.getElementById("nexus-mods-template");
|
||||
const thunderstoreModsTemplate = document.getElementById("thunderstore-mods-template");
|
||||
const settingsTemplate = document.getElementById("settings-template");
|
||||
const installedModTemplate = document.getElementById("installed-mod-template");
|
||||
const modTemplate = document.getElementById("mod-template");
|
||||
@@ -14,18 +15,31 @@ let actualTheme = [];
|
||||
|
||||
let searchValueNexus = "";
|
||||
let searchValueInstalled = "";
|
||||
let searchValueThunderstore = "";
|
||||
|
||||
let onlineSortFilter = "downloads";
|
||||
let installedSortFilter = "name";
|
||||
let thunderstoreSortFilter = "downloads";
|
||||
|
||||
let onlineSortOrder = "DESC";
|
||||
let installedSortOrder = "ASC";
|
||||
let thunderstoreSortOrder = "DESC";
|
||||
|
||||
let onlineOffset = 0;
|
||||
let installedOffset = 0;
|
||||
let thunderstoreOffset = 0;
|
||||
|
||||
let lastOnlineOffset = 0;
|
||||
let lastInstalledOffset = 0;
|
||||
let lastThunderstoreOffset = 0;
|
||||
|
||||
let onlineModsCount = 10;
|
||||
let installedModsCount = 10;
|
||||
let thunderstoreModsCount = 10;
|
||||
|
||||
let onlineModsTotalCount;
|
||||
let installedModsTotalCount;
|
||||
let thunderstoreModsTotalCount;
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
///////////////// CONST FOR WELCOME //////////////////
|
||||
@@ -42,7 +56,6 @@ const welcomeTemplate = document.getElementById("welcome-template");
|
||||
const silksongPathTemplate = document.getElementById("path-template");
|
||||
const nexusTemplate = document.getElementById("nexus-template");
|
||||
const styleTemplate = document.getElementById("style-template");
|
||||
const tutorialTemplate = document.getElementById("tutorial-template");
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
////////////////////// STARTUP ///////////////////////
|
||||
@@ -98,14 +111,15 @@ async function navigate(page) {
|
||||
toggleSelectedListButton("sort-menu", installedSortFilter);
|
||||
setSortOrderButton();
|
||||
|
||||
const { modsInfo, installedTotalCount } = await nexus.getMods(page);
|
||||
const { installedModsInfo, installedTotalCount } = await mods.getMods(page);
|
||||
installedModsTotalCount = installedTotalCount;
|
||||
if (modsInfo == []) {
|
||||
if (installedModsInfo == []) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const modInfo of modsInfo) {
|
||||
for (const modInfo of installedModsInfo) {
|
||||
const installedModTemplateCopy = installedModTemplate.content.cloneNode(true);
|
||||
|
||||
if (modInfo.name) {
|
||||
const modTitleText = installedModTemplateCopy.getElementById("mod-title");
|
||||
modTitleText.innerText = modInfo.name;
|
||||
@@ -138,25 +152,58 @@ async function navigate(page) {
|
||||
const modPicture = installedModTemplateCopy.getElementById("mod-icon");
|
||||
modPicture.src = modInfo.pictureUrl;
|
||||
}
|
||||
if (modInfo.version && modInfo.updatedAt) {
|
||||
if (modInfo.version || modInfo.updatedAt) {
|
||||
let text = "";
|
||||
if (modInfo.version) {
|
||||
text = text.concat(`V${modInfo.version} `);
|
||||
}
|
||||
if (modInfo.updatedAt) {
|
||||
text = text.concat(`last updated on ${modInfo.updatedAt.slice(0, 10)}`);
|
||||
}
|
||||
const modVersionText = installedModTemplateCopy.getElementById("mod-version");
|
||||
modVersionText.innerText = `V${modInfo.version} last updated on ${modInfo.updatedAt.slice(0, 10)}`;
|
||||
modVersionText.innerText = text;
|
||||
}
|
||||
|
||||
const modUrl = `https://www.nexusmods.com/hollowknightsilksong/mods/${modInfo.modId}`;
|
||||
const isActivatedCheckbox = installedModTemplateCopy.getElementById("activated-mod");
|
||||
if (modInfo.activated) {
|
||||
isActivatedCheckbox.checked = true;
|
||||
}
|
||||
|
||||
isActivatedCheckbox.addEventListener("change", async function () {
|
||||
if (this.checked) {
|
||||
mods.activateMods(modInfo.modId);
|
||||
searchInstalledMods();
|
||||
} else {
|
||||
mods.deactivateMods(modInfo.modId);
|
||||
searchInstalledMods();
|
||||
}
|
||||
});
|
||||
|
||||
if (modInfo.source && modInfo.source != "local") {
|
||||
const modSource = installedModTemplateCopy.getElementById("mod-source");
|
||||
modSource.innerText = `From ${modInfo.source}`;
|
||||
|
||||
const modUrls = {
|
||||
nexusmods: `https://www.nexusmods.com/hollowknightsilksong/mods/${modInfo.modId}`,
|
||||
thunderstore: `https://new.thunderstore.io/c/hollow-knight-silksong/p/${modInfo.author}/${modInfo.name}`,
|
||||
};
|
||||
|
||||
const modLinkButton = installedModTemplateCopy.getElementById("external-link");
|
||||
modLinkButton.href = modUrl;
|
||||
modLinkButton.href = modUrls[modInfo.source];
|
||||
modLinkButton.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
const modLink = modLinkButton.href;
|
||||
electronAPI.openExternalLink(modLink);
|
||||
});
|
||||
} else {
|
||||
const modLinkButton = installedModTemplateCopy.getElementById("external-link");
|
||||
modLinkButton.remove();
|
||||
}
|
||||
|
||||
const uninstallModButton = installedModTemplateCopy.getElementById("uninstall-mod-button");
|
||||
uninstallModButton.addEventListener("click", async function (event) {
|
||||
event.preventDefault();
|
||||
await nexus.uninstall(modInfo.modId);
|
||||
await mods.uninstall(modInfo.modId);
|
||||
|
||||
navigate("refresh");
|
||||
});
|
||||
@@ -167,8 +214,8 @@ async function navigate(page) {
|
||||
break;
|
||||
|
||||
case "mods-online":
|
||||
title.innerText = "Online Mods";
|
||||
const onlineModsTemplateCopy = onlineModsTemplate.content.cloneNode(true);
|
||||
title.innerText = "Nexus Mods";
|
||||
const onlineModsTemplateCopy = nexusModsTemplate.content.cloneNode(true);
|
||||
const ModsContainer = onlineModsTemplateCopy.getElementById("mods-container");
|
||||
const searchFormNexus = onlineModsTemplateCopy.getElementById("search-form");
|
||||
const searchInputNexus = onlineModsTemplateCopy.getElementById("search-input");
|
||||
@@ -182,12 +229,12 @@ async function navigate(page) {
|
||||
toggleSelectedListButton("sort-menu", onlineSortFilter);
|
||||
setSortOrderButton();
|
||||
|
||||
const { mods, onlineTotalCount } = await nexus.getMods(page);
|
||||
const { onlineModsInfo, onlineTotalCount } = await mods.getMods(page);
|
||||
onlineModsTotalCount = onlineTotalCount;
|
||||
if (mods == undefined) {
|
||||
if (onlineModsInfo == undefined) {
|
||||
break;
|
||||
}
|
||||
for (const mod of mods) {
|
||||
for (const mod of onlineModsInfo) {
|
||||
if (mod.name == undefined) {
|
||||
continue;
|
||||
}
|
||||
@@ -250,6 +297,89 @@ async function navigate(page) {
|
||||
}
|
||||
break;
|
||||
|
||||
case "mods-thunderstore":
|
||||
title.innerText = "Thunderstore Mods";
|
||||
const thunderstoreModsTemplateCopy = thunderstoreModsTemplate.content.cloneNode(true);
|
||||
const thunderstoreModsContainer = thunderstoreModsTemplateCopy.getElementById("mods-container");
|
||||
const searchFormThunderstore = thunderstoreModsTemplateCopy.getElementById("search-form");
|
||||
const searchInputThunderstore = thunderstoreModsTemplateCopy.getElementById("search-input");
|
||||
|
||||
searchFormThunderstore.addEventListener("submit", async function (event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
searchInputThunderstore.value = searchValueThunderstore;
|
||||
|
||||
view.appendChild(thunderstoreModsTemplateCopy);
|
||||
toggleSelectedListButton("sort-menu", thunderstoreSortFilter);
|
||||
setSortOrderButton();
|
||||
|
||||
const { thunderstoreModsInfo, thunderstoreTotalCount } = await mods.getMods(page);
|
||||
thunderstoreModsTotalCount = thunderstoreTotalCount;
|
||||
if (thunderstoreModsInfo == undefined) {
|
||||
break;
|
||||
}
|
||||
for (const mod of thunderstoreModsInfo) {
|
||||
if (mod.name == undefined) {
|
||||
continue;
|
||||
}
|
||||
const modTemplateCopy = modTemplate.content.cloneNode(true);
|
||||
if (mod.name) {
|
||||
const modTitleText = modTemplateCopy.getElementById("mod-title");
|
||||
modTitleText.innerText = mod.name.replaceAll("_", " ");
|
||||
}
|
||||
if (mod.author) {
|
||||
const modAuthorText = modTemplateCopy.getElementById("mod-author");
|
||||
modAuthorText.innerText = `by ${mod.author}`;
|
||||
}
|
||||
if (mod.endorsements) {
|
||||
const modEndorsementsNumber = modTemplateCopy.getElementById("mod-endorsements-number");
|
||||
if (mod.endorsements > 1) {
|
||||
modEndorsementsNumber.innerText = `${mod.endorsements} likes`;
|
||||
} else {
|
||||
modEndorsementsNumber.innerText = `${mod.endorsements} like`;
|
||||
}
|
||||
}
|
||||
if (mod.downloads) {
|
||||
const modDownloadsNumber = modTemplateCopy.getElementById("mod-downloads-number");
|
||||
if (mod.downloads > 1) {
|
||||
modDownloadsNumber.innerText = `${mod.downloads} downloads`;
|
||||
} else {
|
||||
modDownloadsNumber.innerText = `${mod.downloads} download`;
|
||||
}
|
||||
}
|
||||
if (mod.summary) {
|
||||
const modDescriptionText = modTemplateCopy.getElementById("mod-description");
|
||||
modDescriptionText.innerText = mod.summary;
|
||||
}
|
||||
if (mod.pictureUrl) {
|
||||
const modPicture = modTemplateCopy.getElementById("mod-icon");
|
||||
modPicture.src = mod.pictureUrl;
|
||||
}
|
||||
if (mod.version && mod.updatedAt) {
|
||||
const modVersionText = modTemplateCopy.getElementById("mod-version");
|
||||
modVersionText.innerText = `V${mod.version} last updated on ${mod.updatedAt.slice(0, 10)}`;
|
||||
}
|
||||
|
||||
const modUrl = `https://new.thunderstore.io/c/hollow-knight-silksong/p/${mod.author}/${mod.name}`;
|
||||
|
||||
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 = `https://thunderstore.io/package/download/${mod.author}/${mod.name}/${mod.version}`;
|
||||
thunderstore.download(modDownloadLink, mod.modId);
|
||||
});
|
||||
|
||||
thunderstoreModsContainer.appendChild(modTemplateCopy);
|
||||
}
|
||||
break;
|
||||
case "general-settings":
|
||||
title.innerText = "Settings";
|
||||
const settingsTemplateCopy = settingsTemplate.content.cloneNode(true);
|
||||
@@ -330,6 +460,11 @@ async function welcomeNavigate() {
|
||||
break;
|
||||
|
||||
case 2:
|
||||
pageDiv.appendChild(styleTemplate.content.cloneNode(true));
|
||||
toggleSelectedListButton("themes-menu", actualTheme[0]);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
pageDiv.appendChild(nexusTemplate.content.cloneNode(true));
|
||||
const nexusLink = document.getElementById("external-link");
|
||||
const nexusAPIForm = document.getElementById("nexus-api-form");
|
||||
@@ -346,16 +481,7 @@ async function welcomeNavigate() {
|
||||
setNexusAPI();
|
||||
break;
|
||||
|
||||
case 3:
|
||||
pageDiv.appendChild(styleTemplate.content.cloneNode(true));
|
||||
toggleSelectedListButton("themes-menu", actualTheme[0]);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
pageDiv.appendChild(tutorialTemplate.content.cloneNode(true));
|
||||
break;
|
||||
|
||||
case 5:
|
||||
electronAPI.loadMainPage();
|
||||
break;
|
||||
}
|
||||
@@ -448,12 +574,12 @@ async function setBepinexVersion() {
|
||||
async function searchInstalledMods() {
|
||||
const searchInput = document.getElementById("search-input");
|
||||
searchValueInstalled = searchInput.value;
|
||||
await nexus.searchInstalled(searchValueInstalled, installedOffset, installedModsCount, installedSortFilter, installedSortOrder);
|
||||
await mods.searchInstalled(searchValueInstalled, installedOffset, installedModsCount, installedSortFilter, installedSortOrder);
|
||||
await navigate("refresh");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
/////////////////////// NEXUS ////////////////////////
|
||||
//////////////// NEXUS / THUNDERSTORE ////////////////
|
||||
|
||||
async function verifyNexusAPI() {
|
||||
response = await nexus.verifyAPI();
|
||||
@@ -505,6 +631,20 @@ async function resetNexusAPI() {
|
||||
}
|
||||
}
|
||||
|
||||
async function searchThunderstoreMods() {
|
||||
let searchInput = document.getElementById("search-input");
|
||||
searchValueThunderstore = searchInput.value;
|
||||
await thunderstore.search(searchValueThunderstore, thunderstoreOffset, thunderstoreModsCount, thunderstoreSortFilter, thunderstoreSortOrder);
|
||||
await navigate("refresh");
|
||||
searchInput = document.getElementById("search-input");
|
||||
searchInput.value = searchValueThunderstore;
|
||||
}
|
||||
|
||||
async function addOfflineMod() {
|
||||
await mods.add();
|
||||
searchInstalledMods();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
//////////////// THEMES / SORT / LIST ////////////////
|
||||
|
||||
@@ -583,6 +723,8 @@ function setSortOrderButton() {
|
||||
sortOrder = installedSortOrder;
|
||||
} else if (oldPage == "mods-online") {
|
||||
sortOrder = onlineSortOrder;
|
||||
} else if (oldPage == "mods-thunderstore") {
|
||||
sortOrder = thunderstoreSortOrder;
|
||||
}
|
||||
|
||||
const sortOrderButton = document.getElementById("sort-order-image");
|
||||
@@ -605,6 +747,9 @@ function changeSort(sortFilterParameter) {
|
||||
} else if (oldPage == "mods-online") {
|
||||
onlineSortFilter = sortFilterParameter;
|
||||
searchNexusMods();
|
||||
} else if (oldPage == "mods-thunderstore") {
|
||||
thunderstoreSortFilter = sortFilterParameter;
|
||||
searchThunderstoreMods();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,6 +768,13 @@ function inverseSort() {
|
||||
onlineSortOrder = "ASC";
|
||||
}
|
||||
searchNexusMods();
|
||||
} else if (oldPage == "mods-thunderstore") {
|
||||
if (thunderstoreSortOrder == "ASC") {
|
||||
thunderstoreSortOrder = "DESC";
|
||||
} else {
|
||||
thunderstoreSortOrder = "ASC";
|
||||
}
|
||||
searchThunderstoreMods();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,6 +824,23 @@ function showToast(message, type = "info", duration = 3000) {
|
||||
|
||||
electronAPI.onShowToast(showToast);
|
||||
|
||||
function showBanner(message) {
|
||||
const bannerDiv = document.getElementById("banner-div");
|
||||
const bannerText = document.getElementById("banner-text");
|
||||
|
||||
console.log(bannerDiv);
|
||||
|
||||
bannerText.innerHTML = message;
|
||||
bannerDiv.classList.add("show");
|
||||
}
|
||||
|
||||
electronAPI.onShowBanner(showBanner);
|
||||
|
||||
function hideBanner() {
|
||||
const bannerDiv = document.getElementById("banner-div");
|
||||
bannerDiv.classList.remove("show");
|
||||
}
|
||||
|
||||
function changeModsPage(offsetChange) {
|
||||
if (oldPage == "mods-installed") {
|
||||
if (offsetChange == "min") {
|
||||
@@ -701,6 +870,20 @@ function changeModsPage(offsetChange) {
|
||||
lastOnlineOffset = onlineOffset;
|
||||
searchNexusMods();
|
||||
}
|
||||
} else if (oldPage == "mods-thunderstore") {
|
||||
if (offsetChange == "min") {
|
||||
thunderstoreOffset = 0;
|
||||
} else if (offsetChange == "max") {
|
||||
thunderstoreOffset = thunderstoreModsTotalCount;
|
||||
} else {
|
||||
thunderstoreOffset += thunderstoreModsCount * offsetChange;
|
||||
thunderstoreOffset = clamp(thunderstoreOffset, 0, thunderstoreModsTotalCount);
|
||||
}
|
||||
thunderstoreOffset = Math.floor(thunderstoreOffset / 10) * 10;
|
||||
if (lastThunderstoreOffset != thunderstoreOffset) {
|
||||
lastThunderstoreOffset = thunderstoreOffset;
|
||||
searchThunderstoreMods();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,13 @@ body {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 70px;
|
||||
display: flex;
|
||||
@@ -75,10 +82,6 @@ body {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.invert-color {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.nav {
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -347,7 +350,7 @@ body {
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container {
|
||||
.checkbox-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
@@ -356,13 +359,13 @@ body {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container input {
|
||||
.checkbox-container input {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container .checkmark {
|
||||
.checkbox-container .checkmark {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 30px;
|
||||
@@ -373,27 +376,27 @@ body {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container:hover .checkmark {
|
||||
.checkbox-container:hover .checkmark {
|
||||
background: var(--darker-transparent-black);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container input:checked ~ .checkmark {
|
||||
.checkbox-container input:checked ~ .checkmark {
|
||||
background: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container .checkmark:after {
|
||||
.checkbox-container .checkmark:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container input:checked ~ .checkmark:after {
|
||||
.checkbox-container input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container .checkmark:after {
|
||||
.checkbox-container .checkmark:after {
|
||||
left: 8px;
|
||||
width: 10px;
|
||||
height: 20px;
|
||||
@@ -447,3 +450,19 @@ body {
|
||||
.long-text {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.banner-div {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--secondary-color);
|
||||
background: var(--primary-color);
|
||||
margin-bottom: 20px;
|
||||
height: 64px;
|
||||
border-radius: 12px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.banner-div.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<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>Silk Fly Launcher is a launcher and mod manager for Silksong mods from Nexus and Thunderstore, 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>
|
||||
@@ -47,22 +47,6 @@
|
||||
</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">
|
||||
<img class="nexus-check-image" id="nexus-check-image" src="assets/icons/cross.svg" />
|
||||
<form class="horizontal-div input-form" id="nexus-api-form">
|
||||
<input class="input" id="nexus-api-input" type="text" placeholder="Enter your nexus api" />
|
||||
<button class="default-button" onclick="setNexusAPI()">Set</button>
|
||||
</form>
|
||||
<button class="default-button" onclick="resetNexusAPI()">Reset</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="style-template">
|
||||
<h1 class="title">Chose the theme of the app</h1>
|
||||
<div class="horizontal-div welcome-div">
|
||||
@@ -82,9 +66,27 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="tutorial-template">
|
||||
<h1 class="title">Tutorial to download mod from nexus</h1>
|
||||
<p>After clicking on "Download," a Nexus Mods window will pop up. Please log in, then click on "Download with Mod Manager."The Nexus window should close, and the mod will start downloading.</p>
|
||||
<template id="nexus-template">
|
||||
<h1 class="title">Additional set up for nexus mods</h1>
|
||||
<h2>If you don't want to use nexus mods just click on next.</h2>
|
||||
<br />
|
||||
<br />
|
||||
<p>
|
||||
1. 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>
|
||||
<br />
|
||||
<div class="horizontal-div">
|
||||
<img class="nexus-check-image" id="nexus-check-image" src="assets/icons/cross.svg" />
|
||||
<form class="horizontal-div input-form" id="nexus-api-form">
|
||||
<input class="input" id="nexus-api-input" type="text" placeholder="Enter your nexus api" />
|
||||
<button class="default-button" onclick="setNexusAPI()">Set</button>
|
||||
</form>
|
||||
<button class="default-button" onclick="resetNexusAPI()">Reset</button>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
<p>2. To download a mod from Nexus Mods you need to log in to your Nexus Mods account and then click on "Download with Mod Manager".</p>
|
||||
</template>
|
||||
|
||||
<script src="renderer.js"></script>
|
||||
|
||||