aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
author0neGal <mail@0negal.com>2024-06-08 18:02:48 +0200
committer0neGal <mail@0negal.com>2024-06-08 18:02:48 +0200
commitdbd8c6b152acc1188d2edd288488aa2da8f6310b (patch)
treeef5a1752e845a274c889ee18207c3a25e8290b23 /src/app
parent3904a4492f72ef9a9fd531c0b81f3711541c97e0 (diff)
downloadViper-dbd8c6b152acc1188d2edd288488aa2da8f6310b.tar.gz
Viper-dbd8c6b152acc1188d2edd288488aa2da8f6310b.zip
initial commit to better modularize frontend
Far from done, but this pretty much splits everything inside `src/app/main.js` into separate files.
Diffstat (limited to 'src/app')
-rw-r--r--src/app/css/theming.css2
-rw-r--r--src/app/index.html42
-rw-r--r--src/app/js/browser.js40
-rw-r--r--src/app/js/dom_events.js42
-rw-r--r--src/app/js/events.js2
-rw-r--r--src/app/js/gamepath.js51
-rw-r--r--src/app/js/is_running.js46
-rw-r--r--src/app/js/kill.js8
-rw-r--r--src/app/js/launch.js15
-rw-r--r--src/app/js/launcher.js9
-rw-r--r--src/app/js/mods.js95
-rw-r--r--src/app/js/popups.js6
-rw-r--r--src/app/js/process.js27
-rw-r--r--src/app/js/request.js18
-rw-r--r--src/app/js/set_buttons.js40
-rw-r--r--src/app/js/set_dom_strings.js (renamed from src/app/lang.js)10
-rw-r--r--src/app/js/settings.js529
-rw-r--r--src/app/js/update.js145
-rw-r--r--src/app/main.js557
19 files changed, 890 insertions, 794 deletions
diff --git a/src/app/css/theming.css b/src/app/css/theming.css
index aff3782..fb8fbf7 100644
--- a/src/app/css/theming.css
+++ b/src/app/css/theming.css
@@ -37,7 +37,7 @@ a {
transition: filter 0.2s ease-in !important;
}
-a.disabled:not("[onclick='kill_game()']") {
+a.disabled:not("[onclick='kill('game')']") {
opacity: 0.5;
pointer-events: none;
}
diff --git a/src/app/index.html b/src/app/index.html
index 016b708..87a1029 100644
--- a/src/app/index.html
+++ b/src/app/index.html
@@ -12,7 +12,7 @@
<div id="toasts"></div>
<div id="winbtns">
- <div id="settings" tooltip="%%tooltip.settings%%" tooltip-position="horizontal" onclick="Settings.toggle()">
+ <div id="settings" tooltip="%%tooltip.settings%%" tooltip-position="horizontal" onclick="settings.popup.toggle()">
<img src="icons/settings.png">
</div>
<div id="minimize" tooltip="%%tooltip.minimize%%" tooltip-position="horizontal" onclick="ipcRenderer.send('minimize')">
@@ -34,11 +34,11 @@
<div class="popup" id="options">
<div class="misc">
<input class="search" placeholder="%%gui.search%%">
- <button id="apply" onclick="Settings.apply();Settings.toggle(false)">
+ <button id="apply" onclick="settings.popup.apply();settings.popup.toggle(false)">
<img src="icons/apply.png">
%%gui.settings.save%%
</button>
- <button id="close" onclick="Settings.toggle(false);Settings.load()">
+ <button id="close" onclick="settings.popup.toggle(false);settings.popup.load()">
<img src="icons/close.png">
%%gui.settings.discard%%
</button>
@@ -91,7 +91,7 @@
</div>
</div>
<div class="actions">
- <select onchange="Settings.switch(document.querySelector(`.option[name='autolang'] button`), false)">
+ <select onchange="settings.popup.switch(document.querySelector(`.option[name='autolang'] button`), false)">
<option></option>
</select>
</div>
@@ -147,9 +147,9 @@
</div>
</div>
<div class="actions">
- <button onclick="delete_request_cache()">%%gui.settings.updatebuttons.buttons.reset_cached_api_requests%%</button>
- <button onclick="force_update_ns()" class="disable-when-installing">%%gui.settings.updatebuttons.buttons.force_northstar_reinstall%%</button>
- <button onclick="delete_install_cache()" class="disable-when-installing">%%gui.settings.updatebuttons.buttons.force_delete_install_cache%%</button>
+ <button onclick="request.delete_cache()">%%gui.settings.updatebuttons.buttons.reset_cached_api_requests%%</button>
+ <button onclick="update.ns(true)" class="disable-when-installing">%%gui.settings.updatebuttons.buttons.force_northstar_reinstall%%</button>
+ <button onclick="update.delete_cache()" class="disable-when-installing">%%gui.settings.updatebuttons.buttons.force_delete_install_cache%%</button>
</div>
</div>
</details>
@@ -181,12 +181,12 @@
</div>
</div>
<div class="actions">
- <button onclick="relaunch()">%%gui.settings.miscbuttons.buttons.restart_viper%%</button>
- <button onclick="reset_config()">%%gui.settings.miscbuttons.buttons.reset_config%%</button>
- <button onclick="open_gamepath()">%%gui.settings.miscbuttons.buttons.open_gamepath%%</button>
- <button onclick="kill_game()">%%gui.settings.miscbuttons.buttons.force_quit_game%%</button>
- <button onclick="kill_origin()">%%gui.settings.miscbuttons.buttons.force_quit_origin%%</button>
- <button onclick="setpath()" class="disable-when-installing">%%gui.settings.miscbuttons.buttons.change_gamepath%%</button>
+ <button onclick="process.relaunch()">%%gui.settings.miscbuttons.buttons.restart_viper%%</button>
+ <button onclick="settings.reset()">%%gui.settings.miscbuttons.buttons.reset_config%%</button>
+ <button onclick="gamepath.open()">%%gui.settings.miscbuttons.buttons.open_gamepath%%</button>
+ <button onclick="kill('game')">%%gui.settings.miscbuttons.buttons.force_quit_game%%</button>
+ <button onclick="kill('origin')">%%gui.settings.miscbuttons.buttons.force_quit_origin%%</button>
+ <button onclick="gamepath.set()" class="disable-when-installing">%%gui.settings.miscbuttons.buttons.change_gamepath%%</button>
</div>
</div>
</details>
@@ -247,7 +247,7 @@
<img src="icons/viper.png"/>
<div class="inline" style="margin-top: 20px;">
<div id="vpversion"></div> |
- <a id="setpath" href="#" onclick="setpath()" class="disable-when-installing">%%gui.setpath%%</a>
+ <a id="setpath" href="#" onclick="gamepath.set()" class="disable-when-installing">%%gui.setpath%%</a>
</div>
</div>
<div id="vpReleaseNotes" class="hidden section"></div>
@@ -277,10 +277,10 @@
<div id="nsMain" class="section">
<div class="img"><img src="../assets/ns.png"></div>
<div class="playBtnContainer">
- <button id="playNsBtn" class="playBtn" onclick="launch()">%%gui.launch%%</button>
+ <button id="playNsBtn" class="playBtn" onclick="launch('northstar')">%%gui.launch%%</button>
<div class="inline">
<div id="nsversion"></div>
- <a id="update" href="#" onclick="updateNorthstar()" class="disable-when-installing">(%%gui.update.check%%)</a>
+ <a id="update" href="#" onclick="update.ns()" class="disable-when-installing">(%%gui.update.check%%)</a>
<div id="serverstatus" class="checking"></div>
</div>
</div>
@@ -298,7 +298,7 @@
<img src="icons/toggles.png">
%%gui.mods.toggle_all%%
</button>
- <button id="installmod" class="bg-blue" onclick="installmod()">
+ <button id="installmod" class="bg-blue" onclick="mods.install_prompt()">
<img src="icons/downloads.png">
%%gui.mods.install%%
</button>
@@ -320,10 +320,11 @@
<div class="section">
<div class="img"><img src="../assets/vanilla.png"></div>
<div class="playBtnContainer">
- <button class="playBtn" onclick="launchVanilla()">%%gui.launch%%</button>
+ <button class="playBtn" onclick="launch('vanilla')">%%gui.launch%%</button>
<div class="inline">
<div id="tf2Version"></div>
- <a id="tfquit" style="display: none" href="#" onclick="kill_game()">(%%ns.menu.force_quit%%)</a>
+ <a id="tfquit" style="display: none"
+ href="#" onclick="kill('game')">(%%ns.menu.force_quit%%)</a>
</div>
</div>
</div>
@@ -331,16 +332,13 @@
</div>
</div>
- <script src="lang.js"></script>
<script src="main.js"></script>
<script src="js/misc.js"></script>
<script src="js/mods.js"></script>
<script src="js/toast.js"></script>
- <script src="js/events.js"></script>
<script src="js/popups.js"></script>
<script src="js/tooltip.js"></script>
<script src="js/browser.js"></script>
- <script src="js/settings.js"></script>
<script src="js/launcher.js"></script>
</body>
</html>
diff --git a/src/app/js/browser.js b/src/app/js/browser.js
index 06dc244..00a0933 100644
--- a/src/app/js/browser.js
+++ b/src/app/js/browser.js
@@ -98,7 +98,7 @@ var Browser = {
}
},
install: (package_obj, clear_queue = false) => {
- return installFromURL(
+ return mods.install_from_url(
package_obj.download || package_obj.versions[0].download_url,
package_obj.dependencies || package_obj.versions[0].dependencies,
clear_queue,
@@ -119,9 +119,9 @@ var Browser = {
let remote_version = packages[i].versions[0].version_number;
remote_version = version.format(remote_version);
- if (modsobj) {
- for (let ii = 0; ii < modsobj.all.length; ii++) {
- let mod = modsobj.all[ii];
+ if (mods.list()) {
+ for (let ii = 0; ii < mods.list().all.length; ii++) {
+ let mod = mods.list().all[ii];
if (normalize(mod.name) !== normalized && (
! mod.package ||
@@ -288,14 +288,14 @@ var Browser = {
}
setTimeout(() => {
- for (let i = 0; i < modsobj.all.length; i++) {
- let modname = normalize(modsobj.all[i].name);
- let modfolder = normalize(modsobj.all[i].folder_name);
+ for (let i = 0; i < mods.list().all.length; i++) {
+ let modname = normalize(mods.list().all[i].name);
+ let modfolder = normalize(mods.list().all[i].folder_name);
if (mod.includes(modname)) {
if (! make(modname)) {
- if (modsobj.all[i].manifest_name) {
- make(normalize(modsobj.all[i].manifest_name));
+ if (mods.list().all[i].manifest_name) {
+ make(normalize(mods.list().all[i].manifest_name));
}
}
}
@@ -405,7 +405,7 @@ function BrowserEl(properties) {
let installstr = lang("gui.browser.install");
let normalized_mods = [];
- for (let i = 0; i < modsobj.all; i++) {
+ for (let i = 0; i < mods.list().all; i++) {
normalized_mods.push(normalize(mods_list[i].name));
}
@@ -467,7 +467,7 @@ function add_recent_toast(name, timeout = 3000) {
}
ipcRenderer.on("removed-mod", (event, mod) => {
- setButtons(true);
+ set_buttons(true);
Browser.setbutton(mod.name, lang("gui.browser.install"), "downloads");
if (mod.manifest_name) {
@@ -479,7 +479,7 @@ ipcRenderer.on("failed-mod", (event, modname) => {
if (recent_toasts["failed" + modname]) {return}
add_recent_toast("failed" + modname);
- setButtons(true);
+ set_buttons(true);
new Toast({
timeout: 10000,
scheme: "error",
@@ -492,7 +492,7 @@ ipcRenderer.on("legacy-duped-mod", (event, modname) => {
if (recent_toasts["duped" + modname]) {return}
add_recent_toast("duped" + modname);
- setButtons(true);
+ set_buttons(true);
new Toast({
timeout: 10000,
scheme: "warning",
@@ -502,7 +502,7 @@ ipcRenderer.on("legacy-duped-mod", (event, modname) => {
})
ipcRenderer.on("no-internet", (event, modname) => {
- setButtons(true);
+ set_buttons(true);
new Toast({
timeout: 10000,
scheme: "error",
@@ -517,7 +517,7 @@ ipcRenderer.on("installed-mod", (event, mod) => {
let name = mod.fancy_name || mod.name;
- setButtons(true);
+ set_buttons(true);
Browser.setbutton(name, lang("gui.browser.reinstall"), "redo");
if (mod.malformed) {
@@ -535,13 +535,13 @@ ipcRenderer.on("installed-mod", (event, mod) => {
description: name + " " + lang("gui.toast.desc.installed")
})
- if (installqueue.length != 0) {
- installFromURL(
- "https://thunderstore.io/package/download/" + installqueue[0].pkg,
- false, false, installqueue[0].author, installqueue[0].package_name, installqueue[0].version
+ if (mods.install_queue.length != 0) {
+ mods.install_from_url(
+ "https://thunderstore.io/package/download/" + mods.install_queue[0].pkg,
+ false, false, mods.install_queue[0].author, mods.install_queue[0].package_name, mods.install_queue[0].version
)
- installqueue.shift();
+ mods.install_queue.shift();
}
})
diff --git a/src/app/js/dom_events.js b/src/app/js/dom_events.js
new file mode 100644
index 0000000..73e9998
--- /dev/null
+++ b/src/app/js/dom_events.js
@@ -0,0 +1,42 @@
+const settings = require("./settings");
+
+let drag_timer;
+document.addEventListener("dragover", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ dragUI.classList.add("shown");
+
+ clearTimeout(drag_timer);
+ drag_timer = setTimeout(() => {
+ dragUI.classList.remove("shown");
+ }, 5000)
+})
+
+document.addEventListener("mouseover", () => {
+ clearTimeout(drag_timer);
+ dragUI.classList.remove("shown");
+})
+
+document.addEventListener("drop", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ dragUI.classList.remove("shown");
+ mods.install_from_path(e.dataTransfer.files[0].path);
+})
+
+document.body.addEventListener("keyup", (e) => {
+ if (e.key == "Escape") {
+ Browser.toggle(false);
+ settings.popup.toggle(false);
+ }
+})
+
+document.body.addEventListener("click", (e) => {
+ if (e.target.tagName.toLowerCase() === "a"
+ && e.target.protocol != "file:") {
+
+ e.preventDefault();
+ shell.openExternal(e.target.href);
+ }
+})
diff --git a/src/app/js/events.js b/src/app/js/events.js
index cced4a7..040382e 100644
--- a/src/app/js/events.js
+++ b/src/app/js/events.js
@@ -1,3 +1,5 @@
const EventEmitter = require("events");
class Emitter extends EventEmitter {};
const events = new Emitter();
+
+module.exports = events;
diff --git a/src/app/js/gamepath.js b/src/app/js/gamepath.js
new file mode 100644
index 0000000..80e3158
--- /dev/null
+++ b/src/app/js/gamepath.js
@@ -0,0 +1,51 @@
+const ipcRenderer = require("electron").ipcRenderer;
+
+const lang = require("../../lang");
+const process = require("./process");
+const settings = require("./settings");
+
+// frontend part of settings a new game path
+ipcRenderer.on("newpath", (_, newpath) => {
+ set_buttons(true);
+
+ settings.set({gamepath: newpath});
+
+ ipcRenderer.send("gui-getmods");
+ ipcRenderer.send("save-settings", settings.data());
+})
+
+// a previously valid gamepath no longer exists, and is therefore lost
+ipcRenderer.on("gamepath-lost", () => {
+ page(0);
+ set_buttons(false, true);
+ alert(lang("gui.gamepath.lost"));
+})
+
+// error out when no game path is set
+ipcRenderer.on("no-path-selected", () => {
+ alert(lang("gui.gamepath.must"));
+ process.exit();
+})
+
+// error out when game path is wrong
+ipcRenderer.on("wrong-path", () => {
+ alert(lang("gui.gamepath.wrong"));
+ gamepath.set(false);
+})
+
+// reports to the main process about game path status.
+module.exports = {
+ open: () => {
+ let gamepath = settings.data().gamepath;
+
+ if (gamepath) {
+ require("electron").shell.openPath(gamepath);
+ } else {
+ alert(lang("gui.settings.miscbuttons.open_gamepath_alert"));
+ }
+ },
+
+ set: (value) => {
+ ipcRenderer.send("setpath", value);
+ }
+}
diff --git a/src/app/js/is_running.js b/src/app/js/is_running.js
new file mode 100644
index 0000000..69efd5b
--- /dev/null
+++ b/src/app/js/is_running.js
@@ -0,0 +1,46 @@
+const lang = require("../../lang");
+
+// is the game running?
+let is_running = false;
+
+// updates play buttons depending on whether the game is running
+ipcRenderer.on("is-running", (event, running) => {
+ let set_playbtns = (text) => {
+ let playbtns = document.querySelectorAll(".playBtn");
+ for (let i = 0; i < playbtns.length; i++) {
+ playbtns[i].innerHTML = text;
+ }
+ }
+
+ if (running && is_running != running) {
+ set_buttons(false);
+ set_playbtns(lang("general.running"));
+
+ is_running = running;
+
+ // show force quit button in Titanfall tab
+ tfquit.style.display = "inline-block";
+
+ update.setAttribute("onclick", "kill('game')");
+ update.innerHTML = "(" + lang("ns.menu.force_quit") + ")";
+ return;
+ }
+
+ if (is_running != running) {
+ set_buttons(true);
+ set_playbtns(lang("gui.launch"));
+
+ is_running = running;
+
+ // hide force quit button in Titanfall tab
+ tfquit.style.display = "none";
+
+ update.setAttribute("onclick", "update.ns()");
+ update.innerHTML = "(" + lang("gui.update.check") + ")";
+ }
+})
+
+// return whether the game is running
+module.exports = () => {
+ return is_running;
+}
diff --git a/src/app/js/kill.js b/src/app/js/kill.js
new file mode 100644
index 0000000..04f0a84
--- /dev/null
+++ b/src/app/js/kill.js
@@ -0,0 +1,8 @@
+const ipcRenderer = require("electron").ipcRenderer;
+
+// attempts to kill something using the main process' `modules/kill.js`
+// functions, it simply attempts to run `kill[function_name]()`, if it
+// doesn't exist, nothing happens
+module.exports = (function_name) => {
+ ipcRenderer.send("kill", function_name);
+}
diff --git a/src/app/js/launch.js b/src/app/js/launch.js
new file mode 100644
index 0000000..d14d2ee
--- /dev/null
+++ b/src/app/js/launch.js
@@ -0,0 +1,15 @@
+const update = require("./update");
+
+// tells the main process to launch `game_version`
+module.exports = (game_version) => {
+ if (game_version == "vanilla") {
+ ipcRenderer.send("launch", game_version);
+ return;
+ }
+
+ if (update.ns.should_install) {
+ update.ns();
+ } else {
+ ipcRenderer.send("launch", game_version);
+ }
+}
diff --git a/src/app/js/launcher.js b/src/app/js/launcher.js
index 1c383b4..6fe1686 100644
--- a/src/app/js/launcher.js
+++ b/src/app/js/launcher.js
@@ -4,8 +4,7 @@ var servercount;
var playercount;
var masterserver;
-// Changes the main page
-// This is the tabs in the sidebar
+// changes the main page, this is the tabs in the sidebar
function page(page) {
let btns = document.querySelectorAll(".gamesContainer button");
let pages = document.querySelectorAll(".mainContainer .contentContainer");
@@ -71,7 +70,7 @@ let set_error_content = (div, lang_key) => {
"</div>";
}
-// Updates the Viper release notes
+// updates the Viper release notes
ipcRenderer.on("vp-notes", (event, response) => {
if (! response) {
return set_error_content(
@@ -83,7 +82,7 @@ ipcRenderer.on("vp-notes", (event, response) => {
vpReleaseNotes.innerHTML = formatRelease(response);
});
-// Updates the Northstar release notes
+// updates the Northstar release notes
ipcRenderer.on("ns-notes", (event, response) => {
if (! response) {
return set_error_content(
@@ -208,7 +207,7 @@ async function loadServers() {
}
}; loadServers()
-// Refreshes every 5 minutes
+// refreshes every 5 minutes
setInterval(() => {
loadServers();
}, 300000)
diff --git a/src/app/js/mods.js b/src/app/js/mods.js
index 182bddf..e105c7c 100644
--- a/src/app/js/mods.js
+++ b/src/app/js/mods.js
@@ -1,4 +1,15 @@
-var mods = {};
+let mods = {};
+
+let mods_list = {
+ all: [],
+ enabled: [],
+ disabled: []
+}
+
+// returns the list of mods
+mods.list = () => {
+ return mods_list;
+}
mods.load = (mods_obj) => {
modcount.innerHTML = `${lang("gui.mods.count")} ${mods_obj.all.length}`;
@@ -210,3 +221,85 @@ mods.toggle = (mod) => {
ipcRenderer.send("toggle-mod", mod);
}
+
+mods.install_queue = [];
+
+// tells the main process to install a mod through the file selector
+mods.install_prompt = () => {
+ set_buttons(false);
+ ipcRenderer.send("install-mod");
+}
+
+// tells the main process to directly install a mod from this path
+mods.install_from_path = (path) => {
+ set_buttons(false);
+ ipcRenderer.send("install-from-path", path);
+}
+
+// tells the main process to install a mod from a URL
+mods.install_from_url = (url, dependencies, clearqueue, author, package_name, version) => {
+ if (clearqueue) {mods.install_queue = []};
+
+ let prettydepends = [];
+
+ if (dependencies) {
+ let newdepends = [];
+ for (let i = 0; i < dependencies.length; i++) {
+ let depend = dependencies[i].toLowerCase();
+ if (! depend.match(/northstar-northstar-.*/)) {
+ depend = dependencies[i].replaceAll("-", "/");
+ let pkg = depend.split("/");
+ if (! mods.is_installed(pkg[1])) {
+ newdepends.push({
+ pkg: depend,
+ author: pkg[0],
+ version: pkg[2],
+ package_name: pkg[1]
+ });
+
+ prettydepends.push(`${pkg[1]} v${pkg[2]} - ${lang("gui.browser.made_by")} ${pkg[0]}`);
+ }
+ }
+ }
+
+ dependencies = newdepends;
+ }
+
+ if (dependencies && dependencies.length != 0) {
+ let confirminstall = confirm(lang("gui.mods.confirm_dependencies") + prettydepends.join("\n"));
+ if (! confirminstall) {
+ return;
+ }
+ }
+
+ set_buttons(false);
+ ipcRenderer.send("install-from-url", url, author, package_name, version);
+
+ if (dependencies) {
+ mods.install_queue = dependencies;
+ }
+}
+
+mods.is_installed = (modname) => {
+ for (let i = 0; i < mods.list().all.length; i++) {
+ let mod = mods.list().all[i];
+ if (mod.manifest_name) {
+ if (mod.manifest_name.match(modname)) {
+ return true;
+ }
+ } else if (mod.name.match(modname)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+// updates the installed mods
+ipcRenderer.on("mods", (event, mods_obj) => {
+ mods_list = mods_obj;
+ if (! mods_obj) {return}
+
+ mods.load(mods_obj);
+})
diff --git a/src/app/js/popups.js b/src/app/js/popups.js
index 7411180..10c6995 100644
--- a/src/app/js/popups.js
+++ b/src/app/js/popups.js
@@ -43,7 +43,7 @@ popups.list = () => {
return document.querySelectorAll(".popup");
}
-popups.set_all = (state, exclude_popup) => {
+popups.set_all = (state = false, exclude_popup) => {
let popups_list = document.querySelectorAll(".popup.shown");
for (let i = 0; i < popups_list.length; i++) {
@@ -51,6 +51,8 @@ popups.set_all = (state, exclude_popup) => {
continue;
}
- popups.set(popups_list[i], false, false);
+ popups.set(popups_list[i], state, false);
}
}
+
+module.exports = popups;
diff --git a/src/app/js/process.js b/src/app/js/process.js
new file mode 100644
index 0000000..a4aba6c
--- /dev/null
+++ b/src/app/js/process.js
@@ -0,0 +1,27 @@
+const ipcRenderer = require("electron").ipcRenderer;
+
+ipcRenderer.on("log", (_, msg) => {
+ console.log(msg)
+})
+
+ipcRenderer.on("alert", (_, data) => {
+ alert(data.message);
+ ipcRenderer.send("alert-closed-" + data.id);
+})
+
+ipcRenderer.on("confirm", (_, data) => {
+ let confirmed = confirm(data.message);
+ ipcRenderer.send("confirm-closed-" + data.id, confirmed);
+})
+
+module.exports = {
+ // attempts to relaunch the process
+ relaunch: () => {
+ ipcRenderer.send("relaunch");
+ },
+
+ // attempts to exit the process (closing Viper)
+ exit: () => {
+ ipcRenderer.send("exit")
+ }
+}
diff --git a/src/app/js/request.js b/src/app/js/request.js
new file mode 100644
index 0000000..29b8883
--- /dev/null
+++ b/src/app/js/request.js
@@ -0,0 +1,18 @@
+const ipcRenderer = require("electron").ipcRenderer;
+
+// show a toast message if no Internet connection has been detected.
+if (! navigator.onLine) {
+ ipcRenderer.send("no-internet");
+}
+
+// invokes `requests.get()` from `src/modules/requests.js` through the
+// main process, and returns the output
+let request = async (...args) => {
+ return await ipcRenderer.invoke("request", ...args);
+}
+
+request.delete_cache = () => {
+ ipcRenderer.send("delete-request-cache");
+}
+
+module.exports = request;
diff --git a/src/app/js/set_buttons.js b/src/app/js/set_buttons.js
new file mode 100644
index 0000000..9cb9d3e
--- /dev/null
+++ b/src/app/js/set_buttons.js
@@ -0,0 +1,40 @@
+const ipcRenderer = require("electron").ipcRenderer;
+
+ipcRenderer.on("set-buttons", (_, state) => {
+ set_buttons(state);
+})
+
+// disables or enables certain buttons when for example
+// updating/installing Northstar.
+module.exports = (state, enable_gamepath_btns) => {
+ playNsBtn.disabled = ! state;
+
+ let disable_array = (array) => {
+ for (let i = 0; i < array.length; i++) {
+ array[i].disabled = ! state;
+
+ if (state) {
+ array[i].classList.remove("disabled")
+ } else {
+ array[i].classList.add("disabled")
+ }
+ }
+ }
+
+ disable_array(document.querySelectorAll([
+ "#modsdiv .el button",
+ ".disable-when-installing",
+ ".playBtnContainer .playBtn",
+ "#nsMods .buttons.modbtns button",
+ "#browser #browserEntries .text button",
+ ]))
+
+ if (enable_gamepath_btns) {
+ let gamepath_btns = query_all('*[onclick="gamepath.set()"]');
+
+ for (let i = 0; i < gamepath_btns.length; i++) {
+ gamepath_btns[i].disabled = false;
+ gamepath_btns[i].classList.remove("disabled");
+ }
+ }
+}
diff --git a/src/app/lang.js b/src/app/js/set_dom_strings.js
index f1c31d3..000aba4 100644
--- a/src/app/lang.js
+++ b/src/app/js/set_dom_strings.js
@@ -1,12 +1,12 @@
-// Replaces strings in the HTML will language strings properly. This
+// replaces strings in the HTML will language strings properly. This
// searches for %%<string>%%, aka, %%gui.exit%% will be replaced with
// "Exit", this works without issues.
-function setlang() {
- // Finds %%%% strings
+module.exports = () => {
+ // finds %%%% strings
html = document.body.innerHTML.split("%%");
for (let i = 0; i < html.length; i++) {
- // Simply checks to make sure it is actually a lang string.
+ // simply checks to make sure it is actually a lang string.
if (html[i][0] != " " &&
html[i][html[i].length - 1] != " ") {
// Replaces it with it's string
@@ -14,6 +14,6 @@ function setlang() {
}
}
- // Replaces the original HTML with the translated/replaced HTML
+ // replaces the original HTML with the translated/replaced HTML
document.body.innerHTML = html.join("");
}
diff --git a/src/app/js/settings.js b/src/app/js/settings.js
index 2d68c13..93d1c15 100644
--- a/src/app/js/settings.js
+++ b/src/app/js/settings.js
@@ -1,243 +1,375 @@
-var settings_fuse;
+const fs = require("fs");
+const Fuse = require("fuse.js");
+const ipcRenderer = require("electron").ipcRenderer;
+
+const lang = require("../../lang");
+
+const events = require("./events");
+const popups = require("./popups");
+
+let settings_fuse;
+
+// base settings, and future set settings data
+let settings_data = {
+ nsargs: "",
+ gamepath: "",
+ nsupdate: true,
+ autolang: true,
+ forcedlang: "en",
+ autoupdate: true,
+ originkill: false,
+ zip: "/northstar.zip",
+ lang: navigator.language,
+ excludes: [
+ "ns_startup_args.txt",
+ "ns_startup_args_dedi.txt"
+ ]
+}
-var Settings = {
- toggle: (state) => {
- Settings.load();
- options.scrollTo(0, 0);
+// loads the settings
+if (fs.existsSync("viper.json")) {
+ let iteration = 0;
+
+ // loads the config file, it's loaded in an interval like this in
+ // case the config file is currently being written to, if we were to
+ // read from the file during that, then it'd be empty or similar.
+ //
+ // and because of that, we check if the file is empty when loading
+ // it, if so we wait 100ms, then check again, and we keep doing that
+ // hoping it'll become normal again. unless we've checked it 50
+ // times, then we simply give up, and force the user to re-select
+ // the gamepath, as this'll make the config file readable again.
+ //
+ // ideally it'll never have to check those 50 times, it's only in
+ // case something terrible has gone awry, like if a write got
+ // interrupted, or a user messed with the file.
+ //
+ // were it to truly be broken, then it'd take up to 5 seconds to
+ // then reset, this is basically nothing, especially considering
+ // this should only happen in very rare cases... hopefully!
+ let config_interval = setInterval(() => {
+ let gamepath = require("./gamepath");
+
+ iteration++;
+
+ config = json("viper.json") || {};
+
+ // checks whether `settings_data.gamepath` is set, and if so,
+ // it'll attempt to load ns_startup_args.txt
+ let parse_settings = () => {
+ // if gamepath is not set, attempt to set it
+ if (settings_data.gamepath.length === 0) {
+ gamepath.set(false);
+ } else {
+ // if the gamepath is set, we'll simply tell the main
+ // process about it, and it'll then show the main
+ // renderer BrowserWindow
+ gamepath.set(true);
+ }
- popups.set("#options", state);
- },
- apply: () => {
- settings = {...settings, ...Settings.get()};
- ipcRenderer.send("save-settings", Settings.get());
- },
- get: () => {
- let opts = {};
- let options = document.querySelectorAll(".option");
-
- for (let i = 0; i < options.length; i++) {
- let optName = options[i].getAttribute("name");
- if (options[i].querySelector(".actions input")) {
- let input = options[i].querySelector(".actions input").value;
- if (options[i].getAttribute("type")) {
- opts[optName] = input.split(" ");
- } else {
- opts[optName] = input;
- }
- } else if (options[i].querySelector(".actions select")) {
- opts[optName] = options[i].querySelector(".actions select").value;
- } else if (options[i].querySelector(".actions .switch")) {
- if (options[i].querySelector(".actions .switch.on")) {
- opts[optName] = true;
- } else {
- opts[optName] = false;
- }
+ // filepath to Northstar's startup args file
+ let args = path.join(settings_data.gamepath, "ns_startup_args.txt");
+
+ // check file exists, and that no `nsargs` setting was set
+ if (! settings_data.nsargs && fs.existsSync(args)) {
+ // load arguments from file into `settings`
+ settings_data.nsargs = fs.readFileSync(args, "utf8") || "";
}
}
- return opts;
+ // make sure config isn't empty
+ if (Object.keys(config).length !== 0) {
+ // add `config` to `settings`
+ settings.set(config);
+ parse_settings();
+
+ clearInterval(config_interval);
+ return;
+ }
+
+ // we've attempted to load the config 50 times now, give up
+ if (iteration >= 50) {
+ // request a new gamepath be set
+ gamepath.set(false);
+ clearInterval(config_interval);
+ }
+ }, 100)
+} else {
+ require("./gamepath").set();
+}
+
+ipcRenderer.on("changed-settings", (e, new_settings) => {
+ // attempt to set `settings_data` to `new_settings`
+ try {
+ settings.set(new_settings);
+ }catch(e) {}
+})
+
+let settings = {
+ data: () => {return settings_data},
+
+ // asks the main process to reset the config/settings file
+ reset: () => {
+ ipcRenderer.send("reset-config");
},
- load: () => {
- // re-opens any closed categories
- let categories = document.querySelectorAll("#options details");
- for (let i = 0; i < categories.length; i++) {
- categories[i].setAttribute("open", true);
+
+ // merges `object` with `settings_data`, unless `no_merge` is set,
+ // then it replaces it entirely
+ set: (object, no_merge) => {
+ if (no_merge) {
+ settings_data = object;
+ return;
}
- let options = document.querySelectorAll(".option");
+ settings_data = {
+ ...settings_data,
+ ...object
+ }
+ },
- for (let i = 0; i < options.length; i++) {
- let optName = options[i].getAttribute("name");
- if (optName == "forcedlang") {
- let div = options[i].querySelector("select");
+ popup: {}
+}
- div.innerHTML = "";
- let lang_dir = __dirname + "/../lang";
- let langs = fs.readdirSync(lang_dir);
- for (let i in langs) {
- let lang_file = require(lang_dir + "/" + langs[i]);
- let lang_no_extension = langs[i].replace(/\..*$/, "");
+settings.popup.toggle = (state) => {
+ settings.popup.load();
+ options.scrollTo(0, 0);
- if (! lang_file.lang || ! lang_file.lang.title) {
- continue;
- }
+ popups.set("#options", state);
+}
- let title = lang_file.lang.title;
+settings.popup.apply = () => {
+ settings = {...settings, ...settings.popup.get()};
+ ipcRenderer.send("save-settings", settings.popup.get());
+}
- if (title) {
- div.innerHTML += `<option value="${lang_no_extension}">${title}</option>`
- }
-
- }
+settings.popup.get = () => {
+ let opts = {};
+ let options = document.querySelectorAll(".option");
- div.value = settings.forcedlang;
- continue;
+ for (let i = 0; i < options.length; i++) {
+ let optName = options[i].getAttribute("name");
+ if (options[i].querySelector(".actions input")) {
+ let input = options[i].querySelector(".actions input").value;
+ if (options[i].getAttribute("type")) {
+ opts[optName] = input.split(" ");
+ } else {
+ opts[optName] = input;
}
+ } else if (options[i].querySelector(".actions select")) {
+ opts[optName] = options[i].querySelector(".actions select").value;
+ } else if (options[i].querySelector(".actions .switch")) {
+ if (options[i].querySelector(".actions .switch.on")) {
+ opts[optName] = true;
+ } else {
+ opts[optName] = false;
+ }
+ }
+ }
+
+ return opts;
+}
+
+settings.popup.load = () => {
+ // re-opens any closed categories
+ let categories = document.querySelectorAll("#options details");
+ for (let i = 0; i < categories.length; i++) {
+ categories[i].setAttribute("open", true);
+ }
+
+ let options = document.querySelectorAll(".option");
+
+ for (let i = 0; i < options.length; i++) {
+ let optName = options[i].getAttribute("name");
+ if (optName == "forcedlang") {
+ let div = options[i].querySelector("select");
+
+ div.innerHTML = "";
+ let lang_dir = __dirname + "/../../lang";
+ let langs = fs.readdirSync(lang_dir);
+ for (let i in langs) {
+ let lang_file = require(lang_dir + "/" + langs[i]);
+ let lang_no_extension = langs[i].replace(/\..*$/, "");
+
+ if (! lang_file.lang || ! lang_file.lang.title) {
+ continue;
+ }
- if (settings[optName] != undefined) {
- switch(typeof settings[optName]) {
- case "string":
- options[i].querySelector(".actions input").value = settings[optName];
- break
- case "object":
- options[i].querySelector(".actions input").value = settings[optName].join(" ");
- break
- case "boolean":
- let switchDiv = options[i].querySelector(".actions .switch");
- if (settings[optName]) {
- switchDiv.classList.add("on");
- switchDiv.classList.remove("off");
- } else {
- switchDiv.classList.add("off");
- switchDiv.classList.remove("on");
- }
- break
+ let title = lang_file.lang.title;
+ if (title) {
+ div.innerHTML += `<option value="${lang_no_extension}">${title}</option>`
}
+
}
+
+ div.value = settings_data.forcedlang;
+ continue;
}
- // create Fuse based on options from `get_search_arr()`
- settings_fuse = new Fuse(get_search_arr(), {
- keys: ["text"],
- threshold: 0.4,
- ignoreLocation: true
- })
-
- // reset search
- Settings.search();
- search_el.value = "";
-
- ipcRenderer.send("can-autoupdate");
- ipcRenderer.on("cant-autoupdate", () => {
- document.querySelector(".option[name=autoupdate]")
- .style.display = "none";
-
- document.querySelector(".option[name=autoupdate]")
- .setAttribute("perma-hidden", true);
- })
- },
- switch: (el, state) => {
+ if (settings[optName] != undefined) {
+ switch(typeof settings[optName]) {
+ case "string":
+ options[i].querySelector(".actions input").value = settings[optName];
+ break
+ case "object":
+ options[i].querySelector(".actions input").value = settings[optName].join(" ");
+ break
+ case "boolean":
+ let switchDiv = options[i].querySelector(".actions .switch");
+ if (settings[optName]) {
+ switchDiv.classList.add("on");
+ switchDiv.classList.remove("off");
+ } else {
+ switchDiv.classList.add("off");
+ switchDiv.classList.remove("on");
+ }
+ break
+
+ }
+ }
+ }
+
+ // create Fuse based on options from `get_search_arr()`
+ settings_fuse = new Fuse(get_search_arr(), {
+ keys: ["text"],
+ threshold: 0.4,
+ ignoreLocation: true
+ })
+
+ // reset search
+ settings.popup.search();
+ search_el.value = "";
+
+ ipcRenderer.send("can-autoupdate");
+ ipcRenderer.on("cant-autoupdate", () => {
+ document.querySelector(".option[name=autoupdate]")
+ .style.display = "none";
+
+ document.querySelector(".option[name=autoupdate]")
+ .setAttribute("perma-hidden", true);
+ })
+}
+
+settings.popup.switch = (el, state) => {
+ if (state) {
+ return el.classList.add("on");
+ } else if (state === false) {
+ return el.classList.remove("on");
+ }
+
+ if (el.classList.contains("switch") && el.tagName == "BUTTON") {
+ el.classList.toggle("on");
+ }
+}
+
+// searches for `query` in the list of options, hides the options
+// that dont match, and shows the one that do, if `query` is falsy,
+// it'll simply reset everything back to being visible
+settings.popup.search = (query = "") => {
+ // get list of elements that can be hidden
+ let search_els = [
+ ...document.querySelectorAll(".options .title"),
+ ...document.querySelectorAll(".options .option"),
+ ...document.querySelectorAll(".options .buttons"),
+ ...document.querySelectorAll(".options .details"),
+ ]
+
+ // this sets the visibility of all elements found in
+ // `search_els` unless the element has the `perma-hidden`
+ // attribute set
+ let set_all = (state) => {
+ // set `state` to the CSS equivalent
if (state) {
- return el.classList.add("on");
- } else if (state === false) {
- return el.classList.remove("on");
+ state = null;
+ } else {
+ state = "none";
}
- if (el.classList.contains("switch") && el.tagName == "BUTTON") {
- el.classList.toggle("on");
+ // run through elements
+ for (let i = 0; i < search_els.length; i++) {
+ // check that the element shouldn't be perma hidden, and
+ // if so, hide it correctly.
+ if (search_els[i].hasAttribute("perma-hidden")) {
+ search_els[i].style.display = "none";
+ continue;
+ }
+
+ // set the visibility
+ search_els[i].style.display = state;
}
- },
+ }
+
+ // check if `query` is empty, and reset search if so
+ if (! query || ! query.trim()) {
+ set_all(true);
+ } else {
+ // hide everything
+ set_all(false);
+ }
- // searches for `query` in the list of options, hides the options
- // that dont match, and shows the one that do, if `query` is falsy,
- // it'll simply reset everything back to being visible
- search: (query = "") => {
- // get list of elements that can be hidden
- let search_els = [
- ...document.querySelectorAll(".options .title"),
- ...document.querySelectorAll(".options .option"),
- ...document.querySelectorAll(".options .buttons"),
- ...document.querySelectorAll(".options .details"),
+ // unhides `el` and relevant elements related to it
+ let unhide = (el) => {
+ // list of elements that could be relevant through `el`'s
+ // `.closest()` function
+ let closest = [
+ ".option",
+ "details",
+ ".buttons",
]
- // this sets the visibility of all elements found in
- // `search_els` unless the element has the `perma-hidden`
- // attribute set
- let set_all = (state) => {
- // set `state` to the CSS equivalent
- if (state) {
- state = null;
- } else {
- state = "none";
- }
+ // run through `closest`
+ for (let i = 0; i < closest.length; i++) {
+ // shorthand
+ let closest_el = el.closest(closest[i]);
- // run through elements
- for (let i = 0; i < search_els.length; i++) {
- // check that the element shouldn't be perma hidden, and
- // if so, hide it correctly.
- if (search_els[i].hasAttribute("perma-hidden")) {
- search_els[i].style.display = "none";
- continue;
+ // was a relevant element found?
+ if (closest_el) {
+ // is it supposed to always be hidden? do nothing
+ if (el.hasAttribute("perma-hidden")
+ || closest_el.hasAttribute("perma-hidden")) {
+
+ break;
}
- // set the visibility
- search_els[i].style.display = state;
- }
- }
+ // reset visibility
+ closest_el.style.display = null;
- // check if `query` is empty, and reset search if so
- if (! query || ! query.trim()) {
- set_all(true);
- } else {
- // hide everything
- set_all(false);
- }
-
- // unhides `el` and relevant elements related to it
- let unhide = (el) => {
- // list of elements that could be relevant through `el`'s
- // `.closest()` function
- let closest = [
- ".option",
- "details",
- ".buttons",
- ]
-
- // run through `closest`
- for (let i = 0; i < closest.length; i++) {
- // shorthand
- let closest_el = el.closest(closest[i]);
-
- // was a relevant element found?
- if (closest_el) {
- // is it supposed to always be hidden? do nothing
- if (el.hasAttribute("perma-hidden")
- || closest_el.hasAttribute("perma-hidden")) {
-
- break;
- }
+ // are we at a `<details>`?
+ if (closest[i] == "details") {
+ // attempt to get a `.title` inside that
+ // `<details>` element
+ let title = closest_el.querySelector(".title");
- // reset visibility
- closest_el.style.display = null;
-
- // are we at a `<details>`?
- if (closest[i] == "details") {
- // attempt to get a `.title` inside that
- // `<details>` element
- let title = closest_el.querySelector(".title");
-
- // did we find a title?
- if (title) {
- // reset visibility of title and also reset
- // open state of `<details>`
- title.style.display = null;
- closest_el.setAttribute("open", false);
- }
+ // did we find a title?
+ if (title) {
+ // reset visibility of title and also reset
+ // open state of `<details>`
+ title.style.display = null;
+ closest_el.setAttribute("open", false);
}
}
}
}
+ }
- // do a Fuse.js search with `query`
- let res = settings_fuse.search(query);
+ // do a Fuse.js search with `query`
+ let res = settings_fuse.search(query);
- // if nothing was found, reset all
- if (res.length == 0) {
- return set_all(true);
- }
+ // if nothing was found, reset all
+ if (res.length == 0) {
+ return set_all(true);
+ }
- // run through results and unhide all of them
- for (let i = 0; i < res.length; i++) {
- unhide(res[i].item.el);
- }
+ // run through results and unhide all of them
+ for (let i = 0; i < res.length; i++) {
+ unhide(res[i].item.el);
}
}
// search on key events in search input
let search_el = document.body.querySelector("#options .search");
search_el.addEventListener("keyup", () => {
- Settings.search(search_el.value);
+ settings.popup.search(search_el.value);
})
// returns a Fuse.js compatible Array for searching through the settings
@@ -337,7 +469,12 @@ events.on("popup-changed", () => {
document.body.addEventListener("click", (e) => {
let el = document.elementFromPoint(e.clientX, e.clientY);
- Settings.switch(el);
+ settings.popup.switch(el);
})
-Settings.load();
+settings.popup.load();
+
+// sets the lang to the system default
+ipcRenderer.send("setlang", settings_data.lang);
+
+module.exports = settings;
diff --git a/src/app/js/update.js b/src/app/js/update.js
new file mode 100644
index 0000000..034f3e8
--- /dev/null
+++ b/src/app/js/update.js
@@ -0,0 +1,145 @@
+const ipcRenderer = require("electron").ipcRenderer;
+
+const lang = require("../../lang");
+
+// updates version numbers
+ipcRenderer.on("version", (event, versions) => {
+ vpversion.innerText = versions.vp;
+ nsversion.innerText = versions.ns;
+ tf2Version.innerText = versions.tf2;
+
+ if (versions.ns == "unknown") {
+ let buttons = document.querySelectorAll(".modbtns button");
+
+ for (let i = 0; i < buttons.length; i++) {
+ buttons[i].disabled = true;
+ }
+
+ // since Northstar is not installed, we cannot launch it
+ update.ns.should_install = true;
+ playNsBtn.innerText = lang("gui.install");
+ }
+}); ipcRenderer.send("get-version");
+
+// when an update is available it'll ask the user about it
+ipcRenderer.on("update-available", () => {
+ if (confirm(lang("gui.update.available"))) {
+ ipcRenderer.send("update-now");
+ }
+})
+
+// frontend part of updating Northstar
+ipcRenderer.on("ns-update-event", (event, options) => {
+ let key = options.key;
+ if (typeof options == "string") {
+ key = options;
+ }
+
+ // updates text in update button to `lang(key)`
+ let update_btn = () => {
+ document.getElementById("update")
+ .innerText = `(${lang(key)})`;
+ }
+
+ switch(key) {
+ case "cli.update.uptodate_short":
+ case "cli.update.no_internet":
+ // initial value
+ let delay = 0;
+
+ // get current time
+ let now = new Date().getTime();
+
+ // check if `update.ns.last_checked` was less than 500ms
+ // since now, this variable is set when `update.ns()` is
+ // called
+ if (now - update.ns.last_checked < 500) {
+ // if less than 500ms has passed, set `delay` to the
+ // amount of milliseconds missing until we've hit that
+ // 500ms threshold
+ delay = 500 - (now - update.ns.last_checked);
+ }
+
+ // wait `delay`ms
+ setTimeout(() => {
+ // set buttons accordingly
+ update_btn();
+ set_buttons(true);
+ update.ns.progress(false);
+ playNsBtn.innerText = lang("gui.launch");
+ }, delay)
+
+ break;
+ default:
+ update_btn();
+
+ if (options.progress) {
+ update.ns.progress(options.progress);
+ }
+
+ if (options.btn_text) {
+ playNsBtn.innerText = options.btn_text;
+ }
+
+ set_buttons(false);
+ break;
+ }
+})
+
+let update = {
+ // deletes install and update cache
+ delete_cache: () => {
+ ipcRenderer.send("delete-install-cache");
+ },
+
+ // updates Northstar, `force_update` forcefully updates Northstar,
+ // causing it to update, even if its already up-to-date
+ ns: (force_update) => {
+ update.ns.last_checked = new Date().getTime();
+ ipcRenderer.send("update-northstar", force_update);
+ update.ns.should_install = false;
+ }
+}
+
+// should Northstar be updated?
+update.ns.should_install = false;
+
+// when was the last time we checked for a Northstar update
+update.ns.last_checked = new Date().getTime();
+
+// `percent` should be a number between 0 to 100, if it's `false` it'll
+// reset it back to nothing instantly, with no animation
+update.ns.progress = (percent) => {
+ // reset button progress
+ if (percent === false) {
+ document.querySelector(".contentContainer #nsMain .playBtn")
+ .style.setProperty("--progress", "unset");
+
+ return;
+ }
+
+ percent = parseInt(percent);
+
+ // make sure we're dealing with a number
+ if (isNaN(percent) || typeof percent !== "number") {
+ return false;
+ }
+
+ // limit percent, while this barely has a difference, if you were to
+ // set a very high number, the CSS would then use a very high
+ // number, not great.
+ if (percent > 100) {
+ percent = 100;
+ } else if (percent < 0) {
+ percent = 0;
+ }
+
+ // invert number to it works in the CSS
+ percent = 100 - percent;
+
+ // set the CSS progress variable
+ document.querySelector(".contentContainer #nsMain .playBtn")
+ .style.setProperty("--progress", percent + "%");
+}
+
+module.exports = update;
diff --git a/src/app/main.js b/src/app/main.js
index c3f89d2..2ed3053 100644
--- a/src/app/main.js
+++ b/src/app/main.js
@@ -3,317 +3,7 @@ const path = require("path");
const Fuse = require("fuse.js");
const { app, ipcRenderer, shell } = require("electron");
-const json = require("../modules/json");
-
const lang = require("../lang");
-var modsobj = {
- all: [],
- enabled: [],
- disabled: []
-}
-
-let shouldInstallNorthstar = false;
-
-// Base settings
-var settings = {
- nsargs: "",
- gamepath: "",
- nsupdate: true,
- autolang: true,
- forcedlang: "en",
- autoupdate: true,
- originkill: false,
- zip: "/northstar.zip",
- lang: navigator.language,
- excludes: [
- "ns_startup_args.txt",
- "ns_startup_args_dedi.txt"
- ]
-}
-
-// invokes `requests.get()` from `src/modules/requests.js` through the
-// main process, and returns the output
-async function request(...args) {
- return await ipcRenderer.invoke("request", ...args);
-}
-
-// Sets the lang to the system default
-ipcRenderer.send("setlang", settings.lang);
-
-// Loads the settings
-if (fs.existsSync("viper.json")) {
- let iteration = 0;
-
- // loads the config file, it's loaded in an interval like this in
- // case the config file is currently being written to, if we were to
- // read from the file during that, then it'd be empty or similar.
- //
- // and because of that, we check if the file is empty when loading
- // it, if so we wait 100ms, then check again, and we keep doing that
- // hoping it'll become normal again. unless we've checked it 50
- // times, then we simply give up, and force the user to re-select
- // the gamepath, as this'll make the config file readable again.
- //
- // ideally it'll never have to check those 50 times, it's only in
- // case something terrible has gone awry, like if a write got
- // interrupted, or a user messed with the file.
- //
- // were it to truly be broken, then it'd take up to 5 seconds to
- // then reset, this is basically nothing, especially considering
- // this should only happen in very rare cases... hopefully!
- let config_interval = setInterval(() => {
- iteration++;
-
- config = json("viper.json") || {};
-
- // checks whether `settings.gamepath` is set, and if so,
- // it'll attempt to load ns_startup_args.txt
- let parse_settings = () => {
- // if gamepath is not set, attempt to set it
- if (settings.gamepath.length === 0) {
- setpath(false);
- } else {
- // if the gamepath is set, we'll simply tell the main
- // process about it, and it'll then show the main
- // renderer BrowserWindow
- setpath(true);
- }
-
- // filepath to Northstar's startup args file
- let args = path.join(settings.gamepath, "ns_startup_args.txt");
-
- // check file exists, and that no `nsargs` setting was set
- if (! settings.nsargs && fs.existsSync(args)) {
- // load arguments from file into `settings`
- settings.nsargs = fs.readFileSync(args, "utf8");
- }
- }
-
- // make sure config isn't empty
- if (Object.keys(config).length !== 0) {
- // add `config` to `settings`
- settings = {
- ...settings,
- ...config
- }; parse_settings();
-
- clearInterval(config_interval);
- return;
- }
-
- // we've attempted to load the config 50 times now, give up
- if (iteration >= 50) {
- // request a new gamepath be set
- setpath(false);
- clearInterval(config_interval);
- }
- }, 100)
-} else {
- setpath();
-}
-
-ipcRenderer.on("changed-settings", (e, new_settings) => {
- // attempt to set `settings` to `new_settings`
- try {
- settings = {
- ...settings,
- ...new_settings
- }
- }catch(e) {}
-})
-
-// Show a toast message if no Internet connection has been detected.
-if (! navigator.onLine) {
- ipcRenderer.send("no-internet");
-}
-
-function exit() {ipcRenderer.send("exit")}
-
-let checked_for_ns_update = new Date().getTime();
-
-function updateNorthstar() {
- checked_for_ns_update = new Date().getTime();
- ipcRenderer.send("update-northstar")
-}
-
-function force_update_ns() {
- ipcRenderer.send("update-northstar", true);
-}
-
-function reset_config() {
- ipcRenderer.send("reset-config");
-}
-
-function open_gamepath() {
- let open_path = require("electron").shell.openPath;
- if (settings.gamepath) {
- open_path(settings.gamepath);
- } else {
- alert(lang("gui.settings.miscbuttons.open_gamepath_alert"));
- }
-}
-
-function relaunch() {
- ipcRenderer.send("relaunch");
-}
-
-// Reports to the main process about game path status.
-// @param {boolean} value is game path loaded
-function setpath(value = false) {
- ipcRenderer.send("setpath", value);
-}
-
-// Tells the main process to launch or install Northstar
-function launch() {
- if (shouldInstallNorthstar) {
- updateNorthstar();
- shouldInstallNorthstar = false;
- } else {
- ipcRenderer.send("launch-ns");
- }
-}
-
-// Tells the main process to launch the vanilla game
-function launchVanilla() {ipcRenderer.send("launch-vanilla")}
-
-let log = console.log;
-
-// Disables or enables certain buttons when for example
-// updating/installing Northstar.
-function setButtons(state, enable_gamepath_btns) {
- playNsBtn.disabled = !state;
-
- let disablearray = (array) => {
- for (let i = 0; i < array.length; i++) {
- array[i].disabled = ! state;
-
- if (state) {
- array[i].classList.remove("disabled")
- } else {
- array[i].classList.add("disabled")
- }
- }
- }
-
- disablearray(document.querySelectorAll("#modsdiv .el button"));
- disablearray(document.querySelectorAll(".disable-when-installing"));
- disablearray(document.querySelectorAll(".playBtnContainer .playBtn"));
- disablearray(document.querySelectorAll("#nsMods .buttons.modbtns button"));
- disablearray(document.querySelectorAll("#browser #browserEntries .text button"));
-
- if (enable_gamepath_btns) {
- let gamepath_btns =
- document.querySelectorAll('*[onclick="setpath()"]');
-
- for (let i = 0; i < gamepath_btns.length; i++) {
- gamepath_btns[i].disabled = false;
- gamepath_btns[i].classList.remove("disabled");
- }
- }
-}
-
-// `percent` should be a number between 0 to 100, if it's `false` it'll
-// reset it back to nothing instantly, with no animatino
-function set_ns_progress(percent) {
- // reset button progress
- if (percent === false) {
- document.querySelector(".contentContainer #nsMain .playBtn")
- .style.setProperty("--progress", "unset");
-
- return;
- }
-
- percent = parseInt(percent);
-
- // make sure we're dealing with a number
- if (isNaN(percent) || typeof percent !== "number") {
- return false;
- }
-
- // limit percent, while this barely has a difference, if you were to
- // set a very high number, the CSS would then use a very high
- // number, not great.
- if (percent > 100) {
- percent = 100;
- } else if (percent < 0) {
- percent = 0;
- }
-
- // invert number to it works in the CSS
- percent = 100 - percent;
-
- // set the CSS progress variable
- document.querySelector(".contentContainer #nsMain .playBtn")
- .style.setProperty("--progress", percent + "%");
-}
-
-ipcRenderer.on("set-buttons", (event, state) => {
- setButtons(state);
-})
-
-ipcRenderer.on("gamepath-lost", (event, state) => {
- page(0);
- setButtons(false, true);
- alert(lang("gui.gamepath.lost"));
-})
-
-// Frontend part of updating Northstar
-ipcRenderer.on("ns-update-event", (event, options) => {
- let key = options.key;
- if (typeof options == "string") {
- key = options;
- }
-
- // updates text in update button to `lang(key)`
- let update_btn = () => {
- document.getElementById("update")
- .innerText = `(${lang(key)})`;
- }
-
- switch(key) {
- case "cli.update.uptodate_short":
- case "cli.update.no_internet":
- // initial value
- let delay = 0;
-
- // get current time
- let now = new Date().getTime();
-
- // check if `checked_for_ns_update` was less than 500ms
- // since now, this variable is set when `updateNorthstar()`
- // is called
- if (now - checked_for_ns_update < 500) {
- // if less than 500ms has passed, set `delay` to the
- // amount of milliseconds missing until we've hit that
- // 500ms threshold
- delay = 500 - (now - checked_for_ns_update);
- }
-
- // wait `delay`ms
- setTimeout(() => {
- // set buttons accordingly
- update_btn();
- setButtons(true);
- set_ns_progress(false);
- playNsBtn.innerText = lang("gui.launch");
- }, delay)
-
- break;
- default:
- update_btn();
-
- if (options.progress) {
- set_ns_progress(options.progress);
- }
-
- if (options.btn_text) {
- playNsBtn.innerText = options.btn_text;
- }
-
- setButtons(false);
- break;
- }
-});
ipcRenderer.on("unknown-error", (event, err) => {
new Toast({
@@ -331,238 +21,21 @@ ipcRenderer.on("unknown-error", (event, err) => {
}
})
- console.error(err.stack)
-})
-
-let installqueue = [];
-
-// Tells the main process to install a mod through the file selector
-function installmod() {
- setButtons(false);
- ipcRenderer.send("install-mod");
-}
-
-// Tells the main process to directly install a mod from this path
-function installFromPath(path) {
- setButtons(false);
- ipcRenderer.send("install-from-path", path);
-}
-
-// Tells the main process to install a mod from a URL
-function installFromURL(url, dependencies, clearqueue, author, package_name, version) {
- if (clearqueue) {installqueue = []};
-
- let prettydepends = [];
-
- if (dependencies) {
- let newdepends = [];
- for (let i = 0; i < dependencies.length; i++) {
- let depend = dependencies[i].toLowerCase();
- if (! depend.match(/northstar-northstar-.*/)) {
- depend = dependencies[i].replaceAll("-", "/");
- let pkg = depend.split("/");
- if (! isModInstalled(pkg[1])) {
- newdepends.push({
- pkg: depend,
- author: pkg[0],
- version: pkg[2],
- package_name: pkg[1]
- });
-
- prettydepends.push(`${pkg[1]} v${pkg[2]} - ${lang("gui.browser.made_by")} ${pkg[0]}`);
- }
- }
- }
-
- dependencies = newdepends;
- }
-
- if (dependencies && dependencies.length != 0) {
- let confirminstall = confirm(lang("gui.mods.confirm_dependencies") + prettydepends.join("\n"));
- if (! confirminstall) {
- return
- }
- }
-
- setButtons(false);
- ipcRenderer.send("install-from-url", url, author, package_name, version);
-
- if (dependencies) {
- installqueue = dependencies;
- }
-}
-
-function isModInstalled(modname) {
- for (let i = 0; i < modsobj.all.length; i++) {
- let mod = modsobj.all[i];
- if (mod.manifest_name) {
- if (mod.manifest_name.match(modname)) {
- return true;
- }
- } else if (mod.name.match(modname)) {
- return true;
- }
- }
-
- return false;
-}
-
-// Frontend part of settings a new game path
-ipcRenderer.on("newpath", (event, newpath) => {
- setButtons(true);
- settings.gamepath = newpath;
- ipcRenderer.send("gui-getmods");
- ipcRenderer.send("save-settings", settings);
-})
-
-// Continuation of log()
-ipcRenderer.on("log", (event, msg) => {log(msg)})
-ipcRenderer.on("alert", (event, data) => {
- alert(data.message);
- ipcRenderer.send("alert-closed-" + data.id);
+ console.error(err.stack);
})
-ipcRenderer.on("confirm", (event, data) => {
- let confirmed = confirm(data.message);
- ipcRenderer.send("confirm-closed-" + data.id, confirmed);
-})
-
-let is_running = false;
-ipcRenderer.on("is-running", (event, running) => {
- let set_playbtns = (text) => {
- let playbtns = document.querySelectorAll(".playBtn");
- for (let i = 0; i < playbtns.length; i++) {
- playbtns[i].innerHTML = text;
- }
- }
-
- if (running && is_running != running) {
- setButtons(false);
- set_playbtns(lang("general.running"));
-
- is_running = running;
-
- // show force quit button in Titanfall tab
- tfquit.style.display = "inline-block";
-
- update.setAttribute("onclick", "kill_game()");
- update.innerHTML = "(" + lang("ns.menu.force_quit") + ")";
- return;
- }
-
- if (is_running != running) {
- setButtons(true);
- set_playbtns(lang("gui.launch"));
-
- is_running = running;
-
- // hide force quit button in Titanfall tab
- tfquit.style.display = "none";
-
- update.setAttribute("onclick", "updateNorthstar()");
- update.innerHTML = "(" + lang("gui.update.check") + ")";
- }
-})
-
-function kill_game() {
- ipcRenderer.send("kill-game");
-}
-
-function kill_origin() {
- ipcRenderer.send("kill-origin");
-}
-
-function delete_request_cache() {
- ipcRenderer.send("delete-request-cache");
-}
-
-function delete_install_cache() {
- ipcRenderer.send("delete-install-cache");
-}
-
-// Updates the installed mods
-ipcRenderer.on("mods", (event, mods_obj) => {
- modsobj = mods_obj;
- if (! mods_obj) {return}
-
- mods.load(mods_obj);
-})
-
-// Updates version numbers
-ipcRenderer.on("version", (event, versions) => {
- vpversion.innerText = versions.vp;
- nsversion.innerText = versions.ns;
- tf2Version.innerText = versions.tf2;
-
- if (versions.ns == "unknown") {
- let buttons = document.querySelectorAll(".modbtns button");
-
- for (let i = 0; i < buttons.length; i++) {
- buttons[i].disabled = true;
- }
-
- // Since Northstar is not installed, we cannot launch it
- shouldInstallNorthstar = true;
- playNsBtn.innerText = lang("gui.install");
- }
-}); ipcRenderer.send("get-version");
-
-// When an update is available it'll ask the user about it
-ipcRenderer.on("update-available", () => {
- if (confirm(lang("gui.update.available"))) {
- ipcRenderer.send("update-now");
- }
-})
-
-// Error out when no game path is set
-ipcRenderer.on("no-path-selected", () => {
- alert(lang("gui.gamepath.must"));
- exit();
-});
-
-// Error out when game path is wrong
-ipcRenderer.on("wrong-path", () => {
- alert(lang("gui.gamepath.wrong"));
- setpath(false);
-});
-
-setlang();
-
-let dragtimer;
-document.addEventListener("dragover", (e) => {
- e.preventDefault();
- e.stopPropagation();
- dragUI.classList.add("shown");
-
- clearTimeout(dragtimer);
- dragtimer = setTimeout(() => {
- dragUI.classList.remove("shown");
- }, 5000)
-});
-
-document.addEventListener("mouseover", (e) => {
- clearTimeout(dragtimer);
- dragUI.classList.remove("shown");
-});
-
-document.addEventListener("drop", (e) => {
- event.preventDefault();
- event.stopPropagation();
-
- dragUI.classList.remove("shown");
- installFromPath(event.dataTransfer.files[0].path);
-});
-
-document.body.addEventListener("keyup", (e) => {
- if (e.key == "Escape") {
- Browser.toggle(false);
- Settings.toggle(false);
- }
-})
+const json = require("../modules/json");
-document.body.addEventListener("click", event => {
- if (event.target.tagName.toLowerCase() === "a" && event.target.protocol != "file:") {
- event.preventDefault();
- shell.openExternal(event.target.href);
- }
-});
+const kill = require("./js/kill");
+const update = require("./js/update");
+const events = require("./js/events");
+const launch = require("./js/launch");
+const request = require("./js/request");
+const process = require("./js/process");
+const settings = require("./js/settings");
+const gamepath = require("./js/gamepath");
+const is_running = require("./js/is_running");
+const set_buttons = require("./js/set_buttons");
+
+require("./js/dom_events");
+require("./js/set_dom_strings")();