aboutsummaryrefslogtreecommitdiff
path: root/src/app/js
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/js')
-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.js19
-rw-r--r--src/app/js/settings.js529
-rw-r--r--src/app/js/update.js145
16 files changed, 868 insertions, 224 deletions
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/js/set_dom_strings.js b/src/app/js/set_dom_strings.js
new file mode 100644
index 0000000..000aba4
--- /dev/null
+++ b/src/app/js/set_dom_strings.js
@@ -0,0 +1,19 @@
+// 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.
+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.
+ if (html[i][0] != " " &&
+ html[i][html[i].length - 1] != " ") {
+ // Replaces it with it's string
+ html[i] = lang(html[i]);
+ }
+ }
+
+ // 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;